Leetcode: Surrounded Regions
Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is captured by flipping all 'O's into 'X's in that surrounded region. For example, X X X X X O O X X X O X X O X X After running your function, the board should be: X X X X X X X X X X X X X O X X
难度:92。这个题目用到的方法是图形学中的一个常用方法:Flood fill算法,其实就是从一个点出发对周围区域进行目标颜色的填充。背后的思想就是把一个矩阵看成一个图的结构,每个点看成结点,而边则是他上下左右的相邻点,然后进行一次广度或者深度优先搜索。
这道题首先四个边缘上的‘O’点都不是被surrounded的,这是很直接能看出的,麻烦的是与这些边界上的‘O’点毗邻的其他‘O’点,这些点由于跟边缘上的'O'毗邻,所以也米有被‘X’包裹住。所以我们的想法是:把边界上的‘O’点都找出来,对它们做Flood Fill, 把联通的‘O’区域找出来,把这个区域的点统统由‘O’替换为其他字符比如‘$’。这样没有被替换仍旧为‘O’的那些点,就是被‘X’包裹的。这样整体扫描一次,剩下的所有'O'都应该被替换成'X',而'$'那些最终应该是还原成'O'。
复杂度分析上,我们先对边缘做Flood fill算法,因为只有是'O'才会进行,而且会被替换成'#',所以每个结点改变次数不会超过一次,因而是O(m*n)的复杂度,最后一次遍历同样是O(m*n),所以总的时间复杂度是O(m*n)。
空间上没懂,看了别人的思路。空间上就是递归栈(深度优先搜索)或者是队列(广度优先搜索)的空间,同时存在的空间占用不会超过O(m+n)(以广度优先搜索为例,每次队列中的结点虽然会往四个方向拓展,但是事实上这些结点会有很多重复,假设从中点出发,可以想象最大的扩展不会超过一个菱形,也就是n/2*2+m/2*2=m+n,所以算法的空间复杂度是O(m+n))
方法选择当然DFS和BFS都可以,DFS如果用递归来实现,像Flood Fill算法里那样,图形或者矩阵一般很大,递归容易导致堆栈溢出。所以即使用DFS,也要用Stack来写。
Approach 1: BFS
1 public class Solution { 2 int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, 1}, {0, -1}}; 3 4 public void solve(char[][] board) { 5 if(board == null || board.length == 0 || board[0].length == 0) return; 6 for (int j=0; j<board[0].length; j++) { 7 if (board[0][j] == 'O') bfs(board, 0, j); 8 if (board[board.length-1][j] == 'O') bfs(board, board.length-1, j); 9 } 10 for (int i=0; i<board.length; i++) { 11 if (board[i][0] == 'O') bfs(board, i, 0); 12 if (board[i][board[0].length-1] == 'O') bfs(board, i, board[0].length-1); 13 } 14 for (int i=0; i<board.length; i++) { 15 for (int j=0; j<board[0].length; j++) { 16 if (board[i][j] == 'X') continue; 17 else if (board[i][j] == '$') board[i][j] = 'O'; 18 else if (board[i][j] == 'O') board[i][j] = 'X'; 19 } 20 } 21 } 22 23 public void bfs(char[][] board, int i, int j) { 24 Queue<int[]> queue = new LinkedList<>(); 25 queue.offer(new int[]{i, j}); 26 board[i][j] = '$'; 27 while (queue.size() != 0) { 28 int[] pair = queue.poll(); 29 int row = pair[0]; 30 int col = pair[1]; 31 32 for (int[] dir : dirs) { 33 int x = row + dir[0]; 34 int y = col + dir[1]; 35 if (x >= 0 && x < board.length && y >= 0 && y < board[0].length && board[x][y] == 'O') { 36 queue.offer(new int[]{x, y}); 37 board[x][y] = '$'; 38 } 39 } 40 } 41 } 42 }
1. 注意BFS一般都需要Visited数组,这里访问元素设置为‘$’其实就相当于做了,就不需要额外visited数组了
2. 注意设置一个元素为visited的位置,应该在首次把元素放入queue里面的时候(enqueue时),而不是把元素从queue里poll出来的时候,后者TLE错误(错误原因是:enqueue就一定会dequeue,enqueue了就表示一定会visit,所以一定要在这时设置visited,否则如果enqueue了却不设置为visited,其它相邻的点访问的时候也会把这个已经enqueue的点再次enqueue,就会重复访问)。 如下例子所示:
1----2
| /
| /
3
这个graph1,2,3三个点相邻,假设规则是从queue poll出来的时候才设置visited,那么1点visited以后,2,3两个点都enqueue,但并未visited,2点poll的时候,设置2为visited,同时enqueue 3点。这样3点就enqueue了两次!出错了
3. 这道题设置visited数组比较特殊,完了之后不需要再撤销visited,即不需要把变成“$”的元素变回“O”。因为题目要求就是要保留这种变化
下面是一段用递归写的DFS算法,一旦input很大就stack overflow了
1 public class Solution { 2 public void solve(char[][] board) { 3 if(board == null || board.length == 0 || board[0].length == 0) return; 4 for (int j=0; j<board[0].length; j++) { 5 if (board[0][j] == 'O') dfs(board, 0, j); 6 if (board[board.length-1][j] == 'O') dfs(board, board.length-1, j); 7 } 8 for (int i=0; i<board.length; i++) { 9 if (board[i][0] == 'O') dfs(board, i, 0); 10 if (board[i][board[0].length-1] == 'O') dfs(board, i, board[0].length-1); 11 } 12 for (int i=0; i<board.length; i++) { 13 for (int j=0; j<board[0].length; j++) { 14 if (board[i][j] == '$') board[i][j] = 'O'; 15 else if (board[i][j] == 'O') board[i][j] = 'X'; 16 } 17 } 18 } 19 20 public void dfs(char[][] board, int i, int j) { 21 if (i<0 || i>=board.length || j<0 || j>=board[0].length || board[i][j] != 'O') return; 22 if (board[i][j] == 'O') board[i][j] = '$'; 23 dfs(board, i-1, j); 24 dfs(board, i+1, j); 25 dfs(board, i, j-1); 26 dfs(board, i, j+1); 27 } 28 }
Union Find: Just know this idea, too complex for this problem
1 public class Solution { 2 int rows, cols; 3 4 public void solve(char[][] board) { 5 if(board == null || board.length == 0) return; 6 7 rows = board.length; 8 cols = board[0].length; 9 10 // last one is dummy, all outer O are connected to this dummy 11 UnionFind uf = new UnionFind(rows * cols + 1); 12 int dummyNode = rows * cols; 13 14 for(int i = 0; i < rows; i++) { 15 for(int j = 0; j < cols; j++) { 16 if(board[i][j] == 'O') { 17 if(i == 0 || i == rows-1 || j == 0 || j == cols-1) { 18 uf.union(node(i,j), dummyNode); 19 } 20 else { 21 if(i > 0 && board[i-1][j] == 'O') uf.union(node(i,j), node(i-1,j)); 22 if(i < rows-1 && board[i+1][j] == 'O') uf.union(node(i,j), node(i+1,j)); 23 if(j > 0 && board[i][j-1] == 'O') uf.union(node(i,j), node(i, j-1)); 24 if(j < cols-1 && board[i][j+1] == 'O') uf.union(node(i,j), node(i, j+1)); 25 } 26 } 27 } 28 } 29 30 for(int i = 0; i < rows; i++) { 31 for(int j = 0; j < cols; j++) { 32 if(uf.isConnected(node(i,j), dummyNode)) { 33 board[i][j] = 'O'; 34 } 35 else { 36 board[i][j] = 'X'; 37 } 38 } 39 } 40 } 41 42 int node(int i, int j) { 43 return i * cols + j; 44 } 45 } 46 47 class UnionFind { 48 int [] parents; 49 public UnionFind(int totalNodes) { 50 parents = new int[totalNodes]; 51 for(int i = 0; i < totalNodes; i++) { 52 parents[i] = i; 53 } 54 } 55 56 void union(int node1, int node2) { 57 int root1 = find(node1); 58 int root2 = find(node2); 59 if(root1 != root2) { 60 parents[root2] = root1; 61 } 62 } 63 64 int find(int node) { 65 while(parents[node] != node) { 66 parents[node] = parents[parents[node]]; 67 node = parents[node]; 68 } 69 70 return node; 71 } 72 73 boolean isConnected(int node1, int node2) { 74 return find(node1) == find(node2); 75 } 76 }