1. 程式人生 > 實用技巧 >LeetCode——迷宮 i-ii

LeetCode——迷宮 i-ii

Q:由空地和牆組成的迷宮中有一個球。球可以向上下左右四個方向滾動,但在遇到牆壁前不會停止滾動。當球停下時,可以選擇下一個方向。
給定球的起始位置,目的地和迷宮,判斷球能否在目的地停下。
迷宮由一個0和1的二維陣列表示。 1表示牆壁,0表示空地。你可以假定迷宮的邊緣都是牆壁。起始位置和目的地的座標通過行號和列號給出。

示例 1:
輸入 1: 迷宮由以下二維陣列表示

0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0

輸入 2: 起始位置座標 (rowStart, colStart) = (0, 4)
輸入 3: 目的地座標 (rowDest, colDest) = (4, 4)
輸出: true
解析: 一個可能的路徑是 : 左 -> 下 -> 左 -> 下 -> 右 -> 下 -> 右。

示例 2:
輸入 1: 迷宮由以下二維陣列表示

0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0

輸入 2: 起始位置座標 (rowStart, colStart) = (0, 4)
輸入 3: 目的地座標 (rowDest, colDest) = (3, 2)
輸出: false
解析: 沒有能夠使球停在目的地的路徑。

A:DFS和BFS雙解。
DFS:我們從起始位置開始,每次選擇一條路線進行搜尋,並用一個二維布林陣列 visited 表示是否曾經到達過位置 (i, j) ,若在某一次搜尋到位置 (i, j) 時,visited[i, j] = true,那麼我們可以進行回溯,不必繼續搜尋下去。

    public boolean hasPath(int[][] maze, int[] start, int[] destination) {
        boolean[][] visited = new boolean[maze.length][maze[0].length];
        for (int i = 0; i < maze.length; i++) {
            Arrays.fill(visited[i], false);
        }
        return dfs(maze, start[0], start[1], destination, visited);
    }

    private boolean dfs(int[][] maze, int x, int y, int[] destination, boolean[][] visited) {
        //走過,說明該位置所有情況都考慮了一遍但找不到出路
        if (visited[x][y])
            return false;
        if (x == destination[0] && y == destination[1])
            return true;
        visited[x][y] = true;
        int r = y + 1, l = y - 1, u = x - 1, d = x + 1;
        int m = maze.length, n = maze[0].length;
        //右走到頭後所有結果都走一遍
        while (r < n && maze[x][r] == 0)
            r++;
        if (dfs(maze, x, r - 1, destination, visited))
            return true;
        //左走到頭後所有結果都走一遍
        while (l >= 0 && maze[x][l] == 0)
            l--;
        if (dfs(maze, x, l + 1, destination, visited))
            return true;
        //上走到頭後所有結果都走一遍
        while (u >= 0 && maze[u][y] == 0)
            u--;
        if (dfs(maze, u + 1, y, destination, visited))
            return true;
        //下走到頭後所有結果都走一遍
        while (d < m && maze[d][y] == 0)
            d++;
        if (dfs(maze, d - 1, y, destination, visited))
            return true;
        //上下左右都沒法找到出路
        return false;
    }

BFS:同樣用一個visited把走過的記下來

    private int[] idx = {-1, 0, 1, 0, -1};

    public boolean hasPath(int[][] maze, int[] start, int[] destination) {
        int m = maze.length, n = maze[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < maze.length; i++) {
            Arrays.fill(visited[i], false);
        }
        Queue<int[]> q = new LinkedList<>();
        q.add(start);
        while (!q.isEmpty()) {
            int[] curr = q.poll();
            int x = curr[0];
            int y = curr[1];
            visited[x][y] = true;
            if (x == destination[0] && y == destination[1])
                return true;
            for (int k = 0; k < 4; k++) {
                int nx = x + idx[k];
                int ny = y + idx[k+1];
                while (nx >= 0 && ny >= 0 && nx < maze.length && ny < maze[0].length && maze[nx][ny] == 0) {
                    nx = nx + idx[k];
                    ny = ny + idx[k+1];
                }
                nx = nx - idx[k];
                ny = ny - idx[k+1];
                if(visited[nx][ny])
                    continue;
                q.add(new int[]{nx, ny});
            }
        }
        return false;
    }

Q:由空地和牆組成的迷宮中有一個球。球可以向上下左右四個方向滾動,但在遇到牆壁前不會停止滾動。當球停下時,可以選擇下一個方向。
給定球的起始位置,目的地和迷宮,找出讓球停在目的地的最短距離。距離的定義是球從起始位置(不包括)到目的地(包括)經過的空地個數。如果球無法停在目的地,返回 -1。
迷宮由一個0和1的二維陣列表示。 1表示牆壁,0表示空地。你可以假定迷宮的邊緣都是牆壁。起始位置和目的地的座標通過行號和列號給出。

示例 1:
輸入 1: 迷宮由以下二維陣列表示

0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0

輸入 2: 起始位置座標 (rowStart, colStart) = (0, 4)
輸入 3: 目的地座標 (rowDest, colDest) = (4, 4)
輸出: 12
解析: 一條最短路徑 : left -> down -> left -> down -> right -> down -> right。
總距離為 1 + 1 + 3 + 1 + 2 + 2 + 2 = 12。

示例 2:
輸入 1: 迷宮由以下二維陣列表示

0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0

輸入 2: 起始位置座標 (rowStart, colStart) = (0, 4)
輸入 3: 目的地座標 (rowDest, colDest) = (3, 2)
輸出: -1
解析: 沒有能夠使球停在目的地的路徑。

A:同樣DFS和BFS雙解。
DFS:可以使用深度優先搜尋對整顆搜尋樹進行遍歷。我們從起始位置開始,每次選擇一條路線進行搜尋,並用計數器 count 記錄當前的步數。為了防止重複搜尋,我們需要使用一個二維陣列 distance 記錄從起始位置到 (i, j) 的最小步數 distance[i, j],若在某一次搜尋到位置 (i, j) 時,distance[i, j] 的值小於等於 count,那麼我們可以進行回溯,不必繼續搜尋下去。

    private int[] idx = {-1, 0, 1, 0, -1};

    public int shortestDistance(int[][] maze, int[] start, int[] destination) {
        int[][] distence = new int[maze.length][maze[0].length];
        for (int i = 0; i < maze.length; i++) {
            Arrays.fill(distence[i], Integer.MAX_VALUE);
        }
        distence[start[0]][start[1]] = 0;
        dfs(maze, start[0], start[1], destination, 0, distence);
        return distence[destination[0]][destination[1]] == Integer.MAX_VALUE
                   ? -1 : distence[destination[0]][destination[1]];
    }

    private void dfs(int[][] maze, int x, int y, int[] destination, int count, int[][] distence) {
        for (int k = 0; k < 4; k++) {
            int newCount = count;
            int nx = x + idx[k];
            int ny = y + idx[k + 1];
            while (nx >= 0 && ny >= 0 && nx < maze.length && ny < maze[0].length && maze[nx][ny] == 0) {
                newCount++;
                nx = nx + idx[k];
                ny = ny + idx[k + 1];
            }
            nx = nx - idx[k];
            ny = ny - idx[k + 1];
            if (newCount<distence[nx][ny]){
                distence[nx][ny] = newCount;
                dfs(maze, nx, ny, destination, newCount, distence);
            }
        }
    }

BFS:在一般的廣度優先搜尋中,我們不會經過同一個節點超過一次,但在這道題目中,只要從起始位置到當前節點的步數 count 小於之前記錄的最小步數 distance[i, j],我們就會把 (i, j) 再次加入佇列中。

    private int[] idx = {-1, 0, 1, 0, -1};
    public int shortestDistance(int[][] maze, int[] start, int[] destination) {
        int[][] distence = new int[maze.length][maze[0].length];
        for (int i = 0; i < maze.length; i++) {
            Arrays.fill(distence[i], Integer.MAX_VALUE);
        }
        distence[start[0]][start[1]] = 0;
        Queue<int[]> q = new LinkedList<>();
        q.add(new int[]{start[0], start[1]});
        while (!q.isEmpty()) {
            int[] curr = q.poll();
            int x = curr[0];
            int y = curr[1];
            for (int k = 0; k < 4; k++) {
                int newCount = distence[x][y];
                int nx = x + idx[k];
                int ny = y + idx[k + 1];
                while (nx >= 0 && ny >= 0 && nx < maze.length && ny < maze[0].length && maze[nx][ny] == 0) {
                    newCount++;
                    nx = nx + idx[k];
                    ny = ny + idx[k + 1];
                }
                nx = nx - idx[k];
                ny = ny - idx[k + 1];
                if (newCount < distence[nx][ny]) {
                    distence[nx][ny] = newCount;
                    q.add(new int[]{nx, ny});
                }
            }
        }
        return distence[destination[0]][destination[1]] == Integer.MAX_VALUE
                   ? -1 : distence[destination[0]][destination[1]];
    }