给定一个二维的矩阵,包含 'X'
和 'O'
(字母 O)。java
找到全部被 'X'
围绕的区域,并将这些区域里全部的 'O'
用 'X'
填充。node
示例:面试
X X X X X O O X X X O X X O X X
运行你的函数后,矩阵变为:数组
X X X X X X X X X X X X X O X X
解释:数据结构
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O'
都不会被填充为 'X'
。 任何不在边界上,或不与边界上的 'O'
相连的 'O'
最终都会被填充为 'X'
。若是两个元素在水平或垂直方向相邻,则称它们是“相连”的。分布式
这道题咱们拿到基本就能够肯定是图的dfs、bfs遍历的题目了。题目中解释说被包围的区间不会存在于边界上,因此咱们会想到边界上的o要特殊处理,只要把边界上的o特殊处理了,那么剩下的o替换成x就能够了。问题转化为,如何寻找和边界联通的o,咱们须要考虑以下状况。函数
X X X X X O O X X X O X X O O X
这时候的o是不作替换的。由于和边界是连通的。为了记录这种状态,咱们把这种状况下的o换成#做为占位符,待搜索结束以后,遇到o替换为x(和边界不连通的o);遇到#,替换回o(和边界连通的o)。this
如何寻找和边界联通的o? 从边界出发,对图进行dfs和bfs便可。这里简单总结下dfs和dfs。spa
那么基于上面这种想法,咱们有四种方式实现。code
class Solution { public void solve(char[][] board) { if (board == null || board.length == 0) return; int m = board.length; int n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 从边缘o开始搜索 boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; if (isEdge && board[i][j] == 'O') { dfs(board, i, j); } } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (board[i][j] == 'O') { board[i][j] = 'X'; } if (board[i][j] == '#') { board[i][j] = 'O'; } } } } public void dfs(char[][] board, int i, int j) { if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') { // board[i][j] == '#' 说明已经搜索过了. return; } board[i][j] = '#'; dfs(board, i - 1, j); // 上 dfs(board, i + 1, j); // 下 dfs(board, i, j - 1); // 左 dfs(board, i, j + 1); // 右 } }
非递归的方式,咱们须要记录每一次遍历过的位置,咱们用stack来记录,由于它先进后出的特色。而位置咱们定义一个内部类Pos来标记横坐标和纵坐标。注意的是,在写非递归的时候,咱们每次查看stack顶,可是并不出stack,直到这个位置上下左右都搜索不到的时候出Stack。
class Solution { public class Pos{ int i; int j; Pos(int i, int j) { this.i = i; this.j = j; } } public void solve(char[][] board) { if (board == null || board.length == 0) return; int m = board.length; int n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 从边缘第一个是o的开始搜索 boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; if (isEdge && board[i][j] == 'O') { dfs(board, i, j); } } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (board[i][j] == 'O') { board[i][j] = 'X'; } if (board[i][j] == '#') { board[i][j] = 'O'; } } } } public void dfs(char[][] board, int i, int j) { Stack<Pos> stack = new Stack<>(); stack.push(new Pos(i, j)); board[i][j] = '#'; while (!stack.isEmpty()) { // 取出当前stack 顶, 不弹出. Pos current = stack.peek(); // 上 if (current.i - 1 >= 0 && board[current.i - 1][current.j] == 'O') { stack.push(new Pos(current.i - 1, current.j)); board[current.i - 1][current.j] = '#'; continue; } // 下 if (current.i + 1 <= board.length - 1 && board[current.i + 1][current.j] == 'O') { stack.push(new Pos(current.i + 1, current.j)); board[current.i + 1][current.j] = '#'; continue; } // 左 if (current.j - 1 >= 0 && board[current.i][current.j - 1] == 'O') { stack.push(new Pos(current.i, current.j - 1)); board[current.i][current.j - 1] = '#'; continue; } // 右 if (current.j + 1 <= board[0].length - 1 && board[current.i][current.j + 1] == 'O') { stack.push(new Pos(current.i, current.j + 1)); board[current.i][current.j + 1] = '#'; continue; } // 若是上下左右都搜索不到,本次搜索结束,弹出stack stack.pop(); } } }
dfs非递归的时候咱们用stack来记录状态,而bfs非递归,咱们则用队列来记录状态。和dfs不一样的是,dfs中搜索上下左右,只要搜索到一个知足条件,咱们就顺着该方向继续搜索,因此你能够看到dfs代码中,只要知足条件,就入Stack,而后continue本次搜索,进行下一次搜索,直到搜索到没有知足条件的时候出stack。而bfs中,咱们要把上下左右知足条件的都入队,因此搜索的时候就不能continue。你们能够对比下二者的代码,体会bfs和dfs的差别。
class Solution { public class Pos{ int i; int j; Pos(int i, int j) { this.i = i; this.j = j; } } public void solve(char[][] board) { if (board == null || board.length == 0) return; int m = board.length; int n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 从边缘第一个是o的开始搜索 boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; if (isEdge && board[i][j] == 'O') { bfs(board, i, j); } } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (board[i][j] == 'O') { board[i][j] = 'X'; } if (board[i][j] == '#') { board[i][j] = 'O'; } } } } public void bfs(char[][] board, int i, int j) { Queue<Pos> queue = new LinkedList<>(); queue.add(new Pos(i, j)); board[i][j] = '#'; while (!queue.isEmpty()) { Pos current = queue.poll(); // 上 if (current.i - 1 >= 0 && board[current.i - 1][current.j] == 'O') { queue.add(new Pos(current.i - 1, current.j)); board[current.i - 1][current.j] = '#'; // 没有continue. } // 下 if (current.i + 1 <= board.length - 1 && board[current.i + 1][current.j] == 'O') { queue.add(new Pos(current.i + 1, current.j)); board[current.i + 1][current.j] = '#'; } // 左 if (current.j - 1 >= 0 && board[current.i][current.j - 1] == 'O') { queue.add(new Pos(current.i, current.j - 1)); board[current.i][current.j - 1] = '#'; } // 右 if (current.j + 1 <= board[0].length - 1 && board[current.i][current.j + 1] == 'O') { queue.add(new Pos(current.i, current.j + 1)); board[current.i][current.j + 1] = '#'; } } } }
bfs 通常咱们不会去涉及,并且比较绕,以前咱们惟一A过的用bfs递归的方式是层序遍历二叉树的时候能够用递归的方式。
并查集这种数据结构好像你们不太经常使用,实际上颇有用,我在实际的production code中用过并查集。并查集经常使用来解决连通性的问题,即将一个图中连通的部分划分出来。当咱们判断图中两个点之间是否存在路径时,就能够根据判断他们是否在一个连通区域。 而这道题咱们其实求解的就是和边界的O在一个连通区域的的问题。
并查集的思想就是,同一个连通区域内的全部点的根节点是同一个。将每一个点映射成一个数字。先假设每一个点的根节点就是他们本身,而后咱们以此输入连通的点对,而后将其中一个点的根节点赋成另外一个节点的根节点,这样这两个点所在连通区域又相互连通了。
并查集的主要操做有:
class UnionFind { int[] parents; public UnionFind(int totalNodes) { parents = new int[totalNodes]; for (int i = 0; i < totalNodes; i++) { parents[i] = i; } } // 合并连通区域是经过find来操做的, 即看这两个节点是否是在一个连通区域内. void union(int node1, int node2) { int root1 = find(node1); int root2 = find(node2); if (root1 != root2) { parents[root2] = root1; } } int find(int node) { while (parents[node] != node) { // 当前节点的父节点 指向父节点的父节点. // 保证一个连通区域最终的parents只有一个. parents[node] = parents[parents[node]]; node = parents[node]; } return node; } boolean isConnected(int node1, int node2) { return find(node1) == find(node2); } }
咱们的思路是把全部边界上的O看作一个连通区域。遇到O就执行并查集合并操做,这样全部的O就会被分红两类
因为并查集咱们通常用一维数组来记录,方便查找parants,因此咱们将二维坐标用node函数转化为一维坐标。
public void solve(char[][] board) { if (board == null || board.length == 0) return; int rows = board.length; int cols = board[0].length; // 用一个虚拟节点, 边界上的O 的父节点都是这个虚拟节点 UnionFind uf = new UnionFind(rows * cols + 1); int dummyNode = rows * cols; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (board[i][j] == 'O') { // 遇到O进行并查集操做合并 if (i == 0 || i == rows - 1 || j == 0 || j == cols - 1) { // 边界上的O,把它和dummyNode 合并成一个连通区域. uf.union(node(i, j), dummyNode); } else { // 和上下左右合并成一个连通区域. if (i > 0 && board[i - 1][j] == 'O') uf.union(node(i, j), node(i - 1, j)); if (i < rows - 1 && board[i + 1][j] == 'O') uf.union(node(i, j), node(i + 1, j)); if (j > 0 && board[i][j - 1] == 'O') uf.union(node(i, j), node(i, j - 1)); if (j < cols - 1 && board[i][j + 1] == 'O') uf.union(node(i, j), node(i, j + 1)); } } } } for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (uf.isConnected(node(i, j), dummyNode)) { // 和dummyNode 在一个连通区域的,那么就是O; board[i][j] = 'O'; } else { board[i][j] = 'X'; } } } } int node(int i, int j) { return i * cols + j; } }