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]];
}