본문 바로가기

알고리즘/Programmers

[Programmers / Java] 게임 맵 최단거리 (DFS/BFS)

게임 맵 최단거리

public class 게임_맵_최단거리 {

    static int answer = 0;
    //x,y는 현재위치
    //n,m은 전체 길이
    public static int dfs(int[][] maps,int x, int y, int n, int m, int sum){

        if(x < 0 || x > n-1 || y < 0 || y > m-1){
            return 0;
        }

        if(x == n-1 && y == m-1){
            answer = ++sum;
            System.out.println("x = " + x+"y = " + y);
            System.out.println("sum = " + sum);
            return answer;
        }

        System.out.println("(" + x+", " + y+")   sum = "+sum);
        if(maps[x][y] == 0){
            return 0;
        }

        if(maps[x][y] == 1){
            maps[x][y] = 0;
            sum++;
            dfs(maps, x + 1, y , n , m, sum);
            dfs(maps, x, y + 1 , n , m, sum);
            dfs(maps, x - 1, y , n , m, sum);
            dfs(maps, x, y - 1 , n , m, sum);
        }

        return 0;
    }

    public static int solution(int[][] maps) {
        int n = maps.length;
        int m = maps[0].length;
        int sum = 0;
        if(maps[n-2][m-1] == 0 && maps[n-1][m-2] == 0){
            return -1;
        }
        int dfs = dfs(maps, 0, 0, n, m, sum);
        System.out.println("dfs = " + dfs);

        return answer;
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[][]
                {
                        {1,0,1,1,1},
                        {1,0,1,0,1},
                        {1,0,1,1,1},
                        {1,1,1,0,1},
                        {0,0,0,0,1}
                }));
    }
}

dfs로 풀어봤는데 시간초과가 나고 틀린 부분이 나옵니다. dfs로 풀면 (n,m)에 도착을 해도 남아 있는 1 부분이 있다면 탐색을 하기 때문에 시간초과가 난다고 생각했습니다.

어떤식으로 풀어야지 한번 지나간 길을 false로 다시 만들지? 에 대한 고민을 했고 아래쪽에 답이 있습니다. 

 

그리고 정확성도 잘 안나오는데 왜냐하면 찾아봤더니 (n,m)까지 가는데 거리가 최단경로가 아닐 수 있다는 것입니다.

public class 게임_맵_최단거리 {

    static int answer = 0;
    //x,y는 현재위치
    //n,m은 전체 길이
    public static int dfs(int[][] maps,int x, int y, int n, int m, int sum, int[][] clone){
        clone[x][y] = sum;

        int[] dx = {-1,1,0,0};
        int[] dy = {0,0,1,-1};
        for (int i = 0; i < 4; i++) {
            int xx = x + dx[i];
            int yy = y + dy[i];
            if(0 <= xx && xx < n && 0 <= yy & yy < m && maps[xx][yy] == 1 && clone[xx][yy] > sum+1){
                dfs(maps,xx,yy,n,m,sum+1,clone);
            }
        }
        System.out.println("(" + x+", " + y+")   sum = "+sum);
        return 0;
    }

    public static int solution(int[][] maps) {
        int n = maps.length;
        int m = maps[0].length;
        int[][] clone = new int[n][m];
        for (int i = 0; i < maps.length; i++) {
            for (int j = 0; j < maps[0].length; j++) {
                clone[i][j] = 10001;
            }
        }
        if(maps[n-2][m-1] == 0 && maps[n-1][m-2] == 0){
            return -1;
        }

        dfs(maps, 0, 0, n, m, 1, clone);

        return clone[n-1][m-1];
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[][]
                {
                        {1,0,1,1,1},
                        {1,0,1,0,1},
                        {1,0,1,1,1},
                        {1,1,1,0,1},
                        {0,0,0,0,1}
                }));
    }
}

다른분의 코드를 보고 dfs로 구현해봤습니다. clone 배열은 전부 10001값으로 채워져 있습니다. 그리고 dfs에서는 (-1,0), (1,0),(0,1),(0,-1) 순으로 좌표를 옮겨가면서 내가 방문한 것인지 아닌지 확인합니다.

문제의 예시로

처음에 clone에 있는 값이 한번 들어가면 sum값으로 바뀝니다. 그러면

&& clone[xx][yy] > sum+1

if문의 이 조건과 맞지 않게됩니다.

결국 dfs가 아니라 bfs 로 최소거리를 구해야 합니다.

BFS로 구하기

    public static int solution(int[][] maps) {
        int n = maps.length;
        int m = maps[0].length;

        boolean[][] visited = new boolean[n][m];
        Queue<int[]> queue = new LinkedList<>();
        visited[0][0] = true;
        queue.add(new int[] {0,0,1}); //{현재 위치, 현재위치, 총 수}

        int[] dx = {-1,1,0,0};
        int[] dy = {0,0,1,-1};
        int answer = 0;

        while(queue.isEmpty() == false){
            int[] currPoint = queue.remove();
            if(currPoint[0] == n-1 && currPoint[1] == m-1){
                return currPoint[2];
            }
            for (int i = 0; i < 4; i++) {
                int xx = dx[i] + currPoint[0];
                int yy = dy[i] + currPoint[1];
                if(xx >= 0 && xx < n && yy >= 0 && yy < m){
                    if(maps[xx][yy] == 1 && !visited[xx][yy]){
                        visited[xx][yy] = true;
                        queue.add(new int[] {xx, yy, currPoint[2]+1});
                    }
                }

            }
        }
        return -1;
    }

많은 고민을 했습니다. 어떤식으로 동작하는지 감이 왔지만 그걸 코드로 구현하고 어디에다가 저장하는지 정하는게 어려웠습니다.

Queue 안에 int[]를 생성합니다. 여기에는 [다음x, 다음y, 걸어온count] 가 됩니다. 다음 좌표를 for문으로 돌면서 다음 좌표값이 **범위 안에 있고, 길이있고, 방문하지 않았다면** queue에 넣으면 됩니다. 이 때 주의해야할 점이 **visitied**를 true로 하고 넣어야 하는 점입니다.

 

혹시 본인의 코드가 방문체크를 큐에서 꺼낼 때 하고있는건 아닌지 확인해보세요.
방문체크를 큐에 넣을 때 해야 효율성이 통과됩니다.
그 이유는 만약 꺼낼 때 방문체크를 하게되면, 하나의 블럭을 꺼내서 통로를 탐색할 때, 이미 큐에 들어있는 블럭을 또 큐에 넣을 수도 있기 때문입니다.

1 0 9 10(1) 11
2 0 8 0 10(2)
3 0 7 8 9
4 5 6 0 10(3)
0 0 0 0 11

이 문제의 예시를 예로 들면,
각 블럭의 숫자는 BFS로 탐색되는 차례입니다.
큐에서 10(1)을 먼저 꺼낸다고 하면, 10(1)은 11을 큐에 넣겠죠.
그럼 이제 큐에서 10(2) 가 꺼내집니다.
그런데 10(2)는 11을 아직 방문하지 않은 블럭이라고 판단하고 다시 큐에 넣게 됩니다.
이런 중복을 없애기 위해 큐에 넣을 때 방문했다고 체크해야 합니다.

 

 

 

다른 분의 dfs로 푼 코드입니다.

// dfs로 풀었을 때 효율성 문제 발생
public class shortest_gamemap {
    public static void main(String[] args) {
        int[][] maps = {{1,0,1,1,1},{1,0,1,0,1},{1,0,1,1,1},{1,1,1,0,0},{0,0,0,0,1}};
        shortest_gamemap s = new shortest_gamemap();
        System.out.println(s.solution(maps));
    }
    static int min;
    static boolean visit[][];
    static int[] dx = {-1,0,0,1};
    static int[] dy = {0,1,-1,0};
    public int solution(int[][] maps){
        min = Integer.MAX_VALUE;
        visit = new boolean[maps.length][maps[0].length];
        dfs(0,0,maps,0);
        if(min == Integer.MAX_VALUE){
            return -1;
        }
        return min;
    }

    public void dfs(int x, int y, int[][] maps,int cnt){
        cnt++;
        if(x == maps.length-1 && y == maps[0].length-1){
            min = Math.min(cnt, min);
            return;
        }
        for(int i = 0; i<4;i++){
            int nx = x + dx[i];
            int ny = y + dy[i];

            if(nx<0 || ny <0 || nx >= maps.length || ny>= maps[0].length) continue;

            if(!visit[nx][ny] && maps[nx][ny] == 1){
                visit[nx][ny] = true;
                dfs(nx,ny,maps,cnt);
                visit[nx][ny] = false;
            }
        }
    }
}

1개의 좌표를 따라 쭉 따라가고 visit을 true로 만든 뒤 하나의 dfs스택이 끝났을 때 visit를 false로 만드는 법을 배웠습니다. 이런식으로 하면 여러개의 길을 찾을 수 있을거 같습니다.

갈림길이 나오면 하나의 길로 쭉 dfs로 들어가고, 끝에 다다르면 cnt값을 반환하고 stack을 반환합니다. 그 과정에서 visit했던것을 false로 바꿔줍니다.

 

즉, 갈림길이 나오면 각 길을 따라 갑니다. 그 과정에서 true를 했던것을 false로 바꾸면 모든 갈림길을 끝까지 갈 수 있습니다.

 

출처

https://programmers.co.kr/questions/23794

https://geunzrial.tistory.com/115

728x90