🌸题目
给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
1 | X X X X |
运行你的函数后,矩阵变为:
1 | X X X X |
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
分析
这道题我们拿到基本就可以确定是图的 dfs、bfs 遍历的题目了。题目中解释说被包围的区间不会存在于边界上,所以我们会想到边界上的 O 要特殊处理,只要把边界上的 O 特殊处理了,那么剩下的 O 替换成 XX 就可以了。问题转化为,如何寻找和边界联通的 O,我们需要考虑如下情况。
1 | X X X X |
这时候的0不做替换的。因为和边界是连通的。为了记录这种状态,我们把这
种情况下的O换成#作为占位符,待搜索结束之后,遇到O替换为X (和边界
不连通的O) ;遇倒#,替换回$O(和边界连通的0)。
如何寻找和边界联通的O?从边界出发,对图进行dfs 和bfs 即呵。这里简单
总结下dfs和bfs
- bfs递归。可以想想二叉树中如何递归的进行序遍历。
- bfs递归。一般用队列存储。
- dfs递归。最常用,如二叉树的先序遍历。
- dfs非递归。一般用stack。
那么基于上面这种想法,我们有四种方式实现。
解法一:DFS
1 | class Solution { |
解法二: DFS非递归
非递归的方式,我们需要记录每- -次遍历过的位置 ,我们用stack 来记录,因
为它先进后出的特点。而位置我们定义一一个内部类Pos 来标记横坐标和纵坐
标。注意的是,在写非递归的时候,我们每次查看stack顶,但是并不出stack
,直到这个位置上下左右都搜索不到的时候出Stack
1 | class Solution { |
解法三:BFS非递归
dfs非递归的时候我们用stack来记录状态,而bfs 非递归,我们则用队列来记录状态。和dfs 不同的是,dfs 中搜索上下左右,只要搜索到一一个满足条件,我们就顺着该方向继续搜索,所以你可以看到dfs代码中,只要满足条件,就入Stack ,
然后cont inue本次搜索,进行下一次搜索,直到搜索到没有满足条件的时候出stack 。而dfs 中,我们要把上下左右满足条件的都入队,所以搜索的时候就不能continue 。大家可以对比下两者的代码,体会bfs和dfs 的差异。
1 | class Solution { |
BFS 递归
bfs
一般我们不会去涉及,而且比较绕,之前我们唯一 A
过的用 bfs
递归的方式是层序遍历二叉树的时候可以用递归的方式。
并查集
并查集这种数据结构好像大家不太常用,实际上很有用,我在实际的 production code 中用过并查集。并查集常用来解决连通性的问题,即将一个图中连通的部分划分出来。当我们判断图中两个点之间是否存在路径时,就可以根据判断他们是否在一个连通区域。 而这道题我们其实求解的就是和边界的 O 在一个连通区域的的问题。
并查集的思想就是,同一个连通区域内的所有点的根节点是同一个。将每个点映射成一个数字。先假设每个点的根节点就是他们自己,然后我们以此输入连通的点对,然后将其中一个点的根节点赋成另一个节点的根节点,这样这两个点所在连通区域又相互连通了。
并查集的主要操作有:
find(int m)
:这是并查集的基本操作,查找 m 的根节点。isConnected(int m,int n)
:判断 m,n 两个点是否在一个连通区域。union(int m,int n)
:合并 m,,n 两个点所在的连通区域。
1 | class UnionFind { |
我们的思路是把所有边界上的 O 看做一个连通区域。遇到 O 就执行并查集合并操作,这样所有的 O 就会被分成两类
- 和边界上的 O 在一个连通区域内的。这些 O我们保留。
- 不和边界上的 O 在一个连通区域内的。这些 O 就是被包围的,替换。
由于并查集我们一般用一维数组来记录,方便查找 parants,所以我们将二维坐标用 node 函数转化为一维坐标。
1 | public void solve(char[][] board) { |
题解出自@Ac_pipe