130. 被围绕的区域(Surrounded Regions)

频次 ★★★★ · 难度 🟡 · 高频:字节/阿里/腾讯

题目

m×n 矩阵,把被 ‘X’ 包围的 ‘O’ 翻转为 ‘X’。边界上的 ‘O’ 和与边界相连的 ‘O’ 不被翻转。

示例

输入:
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’ 出发做 DFS/BFS,标记为 ’#‘(不可翻转)。最后遍历矩阵:‘O’ → ‘X’(被包围),’#’ → ‘O’(恢复)。

核心:边界上的 ‘O’ 及其连通区域是安全的,其余 ‘O’ 都要翻转。

代码

public void solve(char[][] board) {
    if (board.length == 0) return;
    int m = board.length, n = board[0].length;
    // 从边界 'O' 出发 DFS 标记
    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';
            else if (board[i][j] == '#') board[i][j] = 'O';
        }
    }
}
 
private void dfs(char[][] board, int i, int j) {
    if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != 'O')
        return;
    board[i][j] = '#';                            // 标记为安全
    dfs(board, i + 1, j);
    dfs(board, i - 1, j);
    dfs(board, i, j + 1);
    dfs(board, i, j - 1);
}

复杂度

  • 时间:O(m × n)
  • 空间:O(m × n) 递归栈

边界条件

  • 空矩阵:直接返回
  • 全 ‘X’:不变
  • 全 ‘O’:全部是边界连通,不变

变式

易错点

  • 逆向思维:从边界出发找安全区域,而不是从内部找被包围区域——后者难以判断
  • 标记用 ’#’ 或其他临时字符,最后恢复——不要直接改 ‘O’ 为 ‘X’,否则会切断连通性
  • 边界遍历时注意四边都检查,不要遗漏

面试追问

  • 为什么从边界出发? 因为只有边界上的 ‘O’ 和与边界连通的 ‘O’ 是安全的。逆向思维:找出不安全的(被包围的)不如找出安全的
  • BFS 怎么做? 同样从边界出发,用队列代替递归

关联题