leetcode:130. 被围绕的区域

题目来源

题目描述

在这里插入图片描述

在这里插入图片描述

class Solution {
public:
    void solve(vector<vector<char>>& board) {

    }
};

题目解析

递归

本题中的矩阵中有三种元素:

  • 字母X
  • 被字母X包围的字母O
  • 没有被字母X包围的字母O

本题要求将所有被字母X包围的字母O都变为X,但是很难判断哪些O是被包围的,哪些O不是被包围的。

本题思考方向应该是,两种不同状态的 O 之间的不同区别。那么题,本质是:一个矩阵中有 3 个不同的元素,但是目前有2种元素(O)被混淆在一起,需要把这三种元素区分开来

在这里插入图片描述
解题的重点放在2种不同 的 O 之间的区别

题目中解释说:任何边界上的O都不会被填充为X。我们可以想到,所有的不被包围的O都直接或者间接的与边界上的O相连,因此问题转换为,如何寻找和边界联通的 O

X X X X
X O O X
X X O X
X O O X

我们可以利用这个性质判断O是否在边界上,具体的说:

  • 对于每一个边界上的O,我们以它的起点,标记所有与它直接或者间接相连的字母O从边缘开始感染
  • 遍历这个矩阵,对于每一个字母
    • 如果该字母被标记过,则该字母为没有被字母 X 包围的字母 O,我们将其还原为字母 O;
    • 如果该字母没有被标记过,则该字母为被字母 X 包围的字母 O,我们将其修改为字母 X。

在这里插入图片描述

class Solution {
    /*
    Solution s;
    vector<vector<char>> board;
    board.push_back({'X', 'O', 'X', 'O', 'X', '0'});
    board.push_back({'O', 'X', 'O', 'X', 'O', 'X'});
    board.push_back({'X', 'O', 'X', 'O', 'X', '0'});
    board.push_back({'O', 'X', 'O', 'X', 'O', 'X'});
    s.solve(board);
     * */
    void dfs(vector<vector<char>>& grid, int x, int y){
        if(x < 0 || y < 0 || x >= grid.size() || y >= grid[0].size() || grid[x][y] != 'O'){
            return;
        }
        grid[x][y] = '#';
        dfs(grid, x - 1, y);
        dfs(grid, x + 1, y);
        dfs(grid, x, y - 1);
        dfs(grid, x, y + 1);
    }
public:
    void solve(vector<vector<char>>& board){
        int m = board.size();
        if(m == 0){
            return;
        }
        int n = board[0].size();


        for (int i = 0; i < m; ++i) {
            if(board[i][0] == 'O'){
                dfs(board, i, 0);
            }
            if(board[i][n - 1] == 'O'){
                dfs(board, i, n - 1);
            }
        }
        for (int j = 0; j < n; ++j) {
            if(board[0][j] == 'O'){
                dfs(board, 0, j);
            }
            if(board[m - 1][j] == 'O'){
                dfs(board, m - 1, 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';
                }
            }
        }
        
    }
};



在这里插入图片描述

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        if (board.empty()) return;
        int rows = board.size(), cols = board[0].size();
        // 从边界开始查找,转换
        for (int i = 0; i < rows; ++i) {
            if (board[i][0] == 'O') bfs(board, i, 0);
            if (board[i][cols - 1] == 'O') bfs(board, i, cols - 1);
        }
        for (int i = 0; i < cols; ++i) {
            if (board[0][i] == 'O') bfs(board, 0, i);
            if (board[rows - 1][i] == 'O') bfs(board, rows - 1, i);
        }
        // 恢复转换 和 按题意设置 X元素
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                if (board[i][j] == 'O') board[i][j] = 'X';
                if (board[i][j] == '#') board[i][j] = 'O';
            }
        }
    }

private:
    void bfs(vector<vector<char>>& board, int i, int j) {
        int m = board.size(), n = board[0].size();
        deque<pair<int, int>> worker{ {i,j} };
        while (worker.size()) {
            auto sub = worker.front();
            auto &row = sub.first, &col = sub.second;
            worker.pop_front();
            if (row < 0 || row == m || col < 0 || col == n
                || board[row][col] != 'O') continue;
            board[row][col] = '#';
            worker.push_back({row + 1, col});
            worker.push_back({row - 1, col});
            worker.push_back({row, col + 1});
            worker.push_back({row, col - 1});
        }
    }
};

并查集

注意,这里必须是四面被围的0才能被换成X,也就是说边角上的O一定不会被围,进一步,与边角上的O相连的O也不会被X包围,因此不会被替换

在这里插入图片描述
在这里插入图片描述
那怎么实现并查集呢?

  • 这里,我们Union-Find底层用一个一维数组来实现(当然也可以二维),构造函数需要传入这个数组的大小,而题目给的是一个二维棋盘。
  • 那么将二维数组怎么用一维数组表示呢?二维数组(x, y)可以转换成x * n + y 这个数(m是棋盘的行数,n是棋盘的列树)。这是二维坐标映射到一维的常用技巧
  • 另外,怎么表示【祖师爷】呢?我们之前描述的「祖师爷」 是虚构的, 需要给他留个位置。索引 [ 0... m ∗ n − 1 ] [0... m*n - 1] [0...mn1]都是棋盘内坐标的一维映射,那么就让这个虚拟的dummy节点占据索引 m ∗ n m * n mn就好了

怎么做?

(1)定义一个坐标转换函数:

    static int index(int i, int j, int w){
        //x、y 表示二维坐标值,w 表示矩阵宽度。
        return i * w + j;
    }

(2)初始化并查集,大小为矩阵的大小加一,多出的哪一个作为所有边界节点的父节点dummy

(3)遍历二维数组,将与边界相连的O与dummy联通

(4)遍历二维数组,将与dummy不相连的O改成X

class Solution {
    class UnionFind{
    private:
        std::vector<int> parent;
        std::vector<int> size;
        std::vector<int> help;
        int cnt;
    public:
        UnionFind(int N){
            cnt = N;
            parent.resize(N);
            size.resize(N);
            help.resize(N);
            for (int i = 0; i < N; ++i) {
                parent[i] = i;
                size[i] = 1;
            }
        }

        void merge(int i,  int j){
            int ri = findRoot(i);
            int rj = findRoot(j);
            if(ri != rj){
                if(size[ri] >= size[rj]){
                    parent[rj] = ri;
                    size[ri] += size[rj];
                }else{
                    parent[ri] = rj;
                    size[rj] += size[ri];
                }
                --cnt;
            }
        }
        
        bool isConnected(int i, int j){
            return findRoot(i) == findRoot(j);
        }
    private:
        int findRoot(int i){
            int hi = 0;
            while (parent[i] != i){
                help[hi++] = i;
                i = parent[i];
            }
            for (hi--;  hi >= 0; --hi) {
                parent[help[hi]] = i;
            }
            return i;
        }
    };

private:
    static int index(int i, int j, int w){
        //x、y 表示二维坐标值,w 表示矩阵宽度。
        return i * w + j;
    }
public:
    void solve(vector<vector<char>>& board) {
        if(board.empty()){
            return;
        }

        int m = board.size();         // 矩阵行数
        int n = board[0].size();      // 矩阵列数(宽度),即第一行元素数
        UnionFind unionFind(m * n + 1);          //  初始化并查集,大小为矩阵大小+1, 给 dummy 留⼀个额外位置
        int dummy = m * n;              // dummy索引
   

        // 与边角上的`O`相连的 O 与 dummy 连通
        std::vector<std::vector<int>> dirs {{1, 0},{-1, 0},{0, 1}, {0, -1}};
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') {
                    // 边界节点
                    if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                        unionFind.merge(index(i, j, n), dummy);
                    } else {
                        // 非边界节点
                        for (auto dir : dirs) {
                            int x = i + dir[0];
                            int y = j + dir[1];
                            if (board[x][y] == 'O') {
                                unionFind.merge(index(i, j, n), index(x, y, n));
                            }
                        }
                    }
                }
            }
        }

       

        // 所有不和 dummy 连通的 O, 都要被替换
        for (int i = 1; i < m - 1; i++){
            for (int j = 1; j < n - 1; j++){
                if (board[i][j] == 'O'){
                    if (!unionFind.isConnected(index(i, j, n), dummy)){
                        board[i][j] = 'X';
                    }
                }
            }
        }
    }
};

二维数组转一维数组

#include<stdio.h>
#include<stdlib.h>

//二维数组转一维
/*以列为主的二维阵列要转为一维阵列时,是将二维阵列由上往下一列一列读入一维阵列,此时
索引的对应公式如下所示,其中row与column是二维阵列索引,loc表示对应的一维阵列索引:
loc = column  +  row*行数*/
int main()
{
    int num[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
    printf("原二维数组:\n");
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%4d", num[i][j]);
        }
        printf("\n");
    }

    printf("进行转换:\n");
    int number[3*3];
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            number[j+i*3] =num[i][j];
        }
    }
    for (int i = 0; i < 3 * 3; i++)
        printf("%d", number[i]);
    printf("\n");
    return 0;
}

类似题目

题目思路
leetcode:130. 被围绕的区域 Surrounded Regions
leetcode:695. 岛屿的最大面积Max Area of Island
leetcode:463. 岛屿的周长 Island Perimeter岛屿的周长就是岛屿方格和非岛屿方格相邻的边的数量。
leetcode:200. 岛屿数量 Number of Islandsdfs、并查集
leetcode:305. 岛屿的数量 Number of Islands II
leetcode:694. 不同岛屿的数量 Number of Distinct Islands
leetcode:711.不同岛屿的数量 II Number of Distinct Islands II
leetcode:1020. 飞地的数量 number-of-enclaves
leetcode:1254. 统计封闭岛屿的数目 number-of-closed-islands只要提前把靠边的陆地都淹掉,然后算出来的就是封闭岛屿了。
leetcode:1905. 统计子岛屿 count-sub-islands
leetcode:286.给每个空房间位上填上该房间到 最近 门的距离 Walls and Gates从门开始扩散
leetcode:489. 扫地机器人 Robot Room Cleaner难点:怎么建立位置坐标?怎么回溯?
leetcode:317. 离建筑物最近的距离 Shortest Distance from All Buildings (1) 从每一个建筑物开始进行广度优先搜索 (2) 在搜索的同时计算每一个空格到这个建筑物的距离 (3) 在搜索的同时将每一个空格到每一个建筑物的距离进行累加,得到每个空格到所有建筑物的距离(4) 取空格到所有建筑物的最小距离
leetcode:323. 无向连通图中的连通分量个数Number of Connected Components in an Undirected Graph 连通图
Rotting Oranges
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值