【Leetcode】130.被包围的区域

题目

给定一个二维的矩阵,包含 '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
复制代码

解释:bash

被围绕的区间不会存在于边界上,换句话说,任何边界上的 '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)。函数

如何寻找和边界联通的o? 从边界出发,对图进行dfs和bfs便可。这里简单总结下dfs和dfs。ui

  • bfs递归。能够想一想二叉树中如何递归的进行层序遍历。
  • bfs非递归。通常用队列存储。
  • dfs递归。最经常使用,如二叉树的先序遍历。
  • dfs非递归。通常用stack。

那么基于上面这种想法,咱们有四种方式实现。this

dfs递归

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); // 右
    }
}
复制代码

dfs 非递归

非递归的方式,咱们须要记录每一次遍历过的位置,咱们用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();
        }
    }
}

复制代码

bfs 非递归

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 递归

bfs 通常咱们不会去涉及,并且比较绕,以前咱们惟一A过的用bfs递归的方式是层序遍历二叉树的时候能够用递归的方式。

并查集

并查集这种数据结构好像你们不太经常使用,实际上颇有用,我在实际的production code中用过并查集。并查集经常使用来解决连通性的问题,即将一个图中连通的部分划分出来。当咱们判断图中两个点之间是否存在路径时,就能够根据判断他们是否在一个连通区域。 而这道题咱们其实求解的就是和边界的O在一个连通区域的的问题。

并查集的思想就是,同一个连通区域内的全部点的根节点是同一个。将每一个点映射成一个数字。先假设每一个点的根节点就是他们本身,而后咱们以此输入连通的点对,而后将其中一个点的根节点赋成另外一个节点的根节点,这样这两个点所在连通区域又相互连通了。 并查集的主要操做有:

  • find(int m):这是并查集的基本操做,查找m的根节点。

  • isConnected(int m,int n):判断m,n两个点是否在一个连通区域。

  • union(int m,int n):合并m,n两个点所在的连通区域。

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就会被分红两类

  • 和边界上的O在一个连通区域内的。这些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;
    }
}
复制代码

热门阅读

Leetcode名企之路
相关文章
相关标签/搜索