一、题目与目标
给定一个 m x n 的矩阵 board,元素为 'X' 或 'O':
-
被 全部
'X'包围的'O'区域要被翻转为'X' -
与边界联通的
'O'不能 被翻转
要求:原地修改 board。
二、解题核心思路
反向思考:
-
真正不能被翻转的是:和边界上的
'O'连通的所有'O' -
其他
'O'一定被'X'包围,可以翻成'X'
做法:
-
从四条边上所有为
'O'的格子出发,用 DFS(或 BFS)遍历,把所有和边界连通的'O'暂时标记为'#' -
全图扫描:
-
遇到
'O':说明它不与边界连通 → 翻成'X' -
遇到
'#':说明它和边界连通 → 还原成'O'
-
这样就能区分"能翻转的 O"和"不能翻转的 O"。
三、算法步骤
假设 m 为行数,n 为列数。
-
处理左右边界 (第 0 列和第 n-1 列)
对每一行
i:-
如果
board[i][0] == 'O',从(i, 0)开始 DFS,标记连通块为'#' -
如果
board[i][n - 1] == 'O',从(i, n-1)开始 DFS
-
-
处理上下边界 (第 0 行和第 m-1 行)
对每一列
j:-
如果
board[0][j] == 'O',从(0, j)开始 DFS -
如果
board[m - 1][j] == 'O',从(m-1, j)开始 DFS
-
-
全表扫描进行翻转
双重循环遍历整个矩阵:
-
若
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); // 上
}
};
说明:
m和n在dfs中按引用传递只是为了少拷贝,按值传也可以,不影响逻辑。
2. 关键点解析
-
从边界开始 DFS 而不是从内部
所有从边界
'O'出发能走到的'O'都是"安全区",临时标记为'#'保留。 -
DFS 的访问条件
-
先判断越界:
upDown和lr在[0, m-1]、[0, n-1]范围内 -
再判断是否为
'O',不是'O'直接返回
-
-
标记与最终翻转的分离
-
DFS 只负责标记
'#' -
最后统一遍历一遍矩阵进行 翻转 + 还原
-
这样保证逻辑清晰、实现简单。
五、时间与空间复杂度
-
每个格子最多被访问和标记一次
→ 时间复杂度 :
O(m * n) -
递归调用栈最坏深度为
O(m * n)(极端全为'O'的情况)→ 空间复杂度 :最坏
O(m * n)(递归栈)
六、小结要点
-
从 四条边 上的
'O'出发,找出所有和边界联通的'O' -
用临时字符
'#'标记"边界联通区" -
最后统一遍历:
-
'O'→'X' -
'#'→'O'
-
整套解法就是「边界 DFS + 反向思考」模型