LeetCode--130被围绕的区域

一、题目与目标

给定一个 m x n 的矩阵 board,元素为 'X''O'

  • 全部 'X' 包围的 'O' 区域要被翻转为 'X'

  • 与边界联通的 'O' 不能 被翻转

要求:原地修改 board


二、解题核心思路

反向思考

  • 真正不能被翻转的是:和边界上的 'O' 连通的所有 'O'

  • 其他 'O' 一定被 'X' 包围,可以翻成 'X'

做法:

  1. 从四条边上所有为 'O' 的格子出发,用 DFS(或 BFS)遍历,把所有和边界连通的 'O' 暂时标记为 '#'

  2. 全图扫描:

    • 遇到 'O':说明它不与边界连通 → 翻成 'X'

    • 遇到 '#':说明它和边界连通 → 还原成 'O'

这样就能区分"能翻转的 O"和"不能翻转的 O"。


三、算法步骤

假设 m 为行数,n 为列数。

  1. 处理左右边界 (第 0 列和第 n-1 列)

    对每一行 i

    • 如果 board[i][0] == 'O',从 (i, 0) 开始 DFS,标记连通块为 '#'

    • 如果 board[i][n - 1] == 'O',从 (i, n-1) 开始 DFS

  2. 处理上下边界 (第 0 行和第 m-1 行)

    对每一列 j

    • 如果 board[0][j] == 'O',从 (0, j) 开始 DFS

    • 如果 board[m - 1][j] == 'O',从 (m-1, j) 开始 DFS

  3. 全表扫描进行翻转

    双重循环遍历整个矩阵:

    • board[i][j] == 'O':翻成 'X'

    • board[i][j] == '#':还原成 'O'


四、代码实现与解析

1. 完整代码

cpp 复制代码
class Solution {
public:
    void solve(vector<vector<char>>& board) {
        // m 表示行数,n 表示列数
        int m = board.size(), n = board[0].size();

        // 1. 处理左右边界
        for (int i = 0; i < m; ++i) {
            if (board[i][0] == 'O') {
                dfs(i, 0, board, n, m);
            }
            if (board[i][n - 1] == 'O') {
                dfs(i, n - 1, board, n, m);
            }
        }

        // 2. 处理上下边界
        for (int j = 0; j < n; ++j) {
            if (board[0][j] == 'O') {
                dfs(0, j, board, n, m);
            }
            if (board[m - 1][j] == 'O') {
                dfs(m - 1, j, board, n, m);
            }
        }

        // 3. 全表扫描,翻转 / 还原
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (board[i][j] == 'O') {
                    board[i][j] = 'X';     // 被包围,翻转
                } else if (board[i][j] == '#') {
                    board[i][j] = 'O';     // 与边界连通,还原
                }
            }
        }
    }

private:
    // upDown: 行下标,lr: 列下标
    void dfs(int upDown, int lr,
             vector<vector<char>>& board,
             int& n, int& m) {

        // 越界直接返回
        if (upDown < 0 || lr < 0 || lr > n - 1 || upDown > m - 1) return;

        // 只处理 'O'
        if (board[upDown][lr] != 'O') return;

        // 标记为 '#'
        board[upDown][lr] = '#';

        // 四个方向扩展
        dfs(upDown,     lr + 1, board, n, m); // 右
        dfs(upDown,     lr - 1, board, n, m); // 左
        dfs(upDown + 1, lr,     board, n, m); // 下
        dfs(upDown - 1, lr,     board, n, m); // 上
    }
};

说明:mndfs 中按引用传递只是为了少拷贝,按值传也可以,不影响逻辑。


2. 关键点解析

  1. 从边界开始 DFS 而不是从内部

    所有从边界 'O' 出发能走到的 'O' 都是"安全区",临时标记为 '#' 保留。

  2. DFS 的访问条件

    • 先判断越界:upDownlr[0, m-1][0, n-1] 范围内

    • 再判断是否为 'O',不是 'O' 直接返回

  3. 标记与最终翻转的分离

    • DFS 只负责标记 '#'

    • 最后统一遍历一遍矩阵进行 翻转 + 还原

这样保证逻辑清晰、实现简单。


五、时间与空间复杂度

  • 每个格子最多被访问和标记一次

    时间复杂度O(m * n)

  • 递归调用栈最坏深度为 O(m * n)(极端全为 'O' 的情况)

    空间复杂度 :最坏 O(m * n)(递归栈)


六、小结要点

  1. 四条边 上的 'O' 出发,找出所有和边界联通的 'O'

  2. 用临时字符 '#' 标记"边界联通区"

  3. 最后统一遍历:

    • 'O''X'

    • '#''O'

整套解法就是「边界 DFS + 反向思考」模型

相关推荐
烛衔溟41 分钟前
C语言算法:动态规划基础
c语言·算法·动态规划·算法设计·dp基础
ouliten41 分钟前
C++笔记:std::priority_queue
c++·笔记
cookies_s_s43 分钟前
项目--协程库(C++)模块解析篇
服务器·c++
止观止1 小时前
C++20 Modules:终结“头文件地狱”的曙光
c++·c++20·头文件·modules·编译优化
誰能久伴不乏1 小时前
进程通信与线程通信:全面总结 + 使用场景 + 优缺点 + 使用方法
linux·服务器·c语言·c++
fish_xk1 小时前
用c++写控制台贪吃蛇
开发语言·c++
念越1 小时前
二叉树的核心概念与遍历实现(基础详解版)
数据结构
im_AMBER1 小时前
数据结构 12 图
数据结构·笔记·学习·算法·深度优先
程序员-周李斌1 小时前
LinkedList 源码深度分析(基于 JDK 8)
java·开发语言·数据结构·list