1. 题目概述与核心思路
LeetCode 130题"Surrounded Regions"是一道经典的矩阵遍历问题,考察图的深度优先搜索(DFS)和广度优先搜索(BFS)应用能力。题目要求我们将被'X'完全包围的'O'区域全部转换为'X',而任何与边界相连的'O'区域则保持不变。
这个问题的关键在于理解"被包围"的定义——只有当'O'区域的所有边界都由'X'组成,且不与矩阵边缘相连时,才需要进行转换。因此,逆向思维往往更有效:先标记所有不会被转换的'O'(即与边界相连的区域),然后再处理剩余区域。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法选择与复杂度分析
2.1 DFS与BFS的抉择
对于这类矩阵遍历问题,DFS和BFS都是可行的解决方案。DFS的实现通常更简洁,适合快速编码;而BFS在极端情况下(如矩阵非常大时)可以避免栈溢出问题。以下是两种方法的对比:
| 特性 | DFS | BFS |
|---|---|---|
| 空间复杂度 | O(mn)最坏情况 | O(min(m,n)) |
| 实现难度 | 递归简单 | 需要队列数据结构 |
| 适用场景 | 常规情况 | 超大矩阵或深度优先受限环境 |
2.2 边界处理技巧
高效解决本题的关键在于从边界出发的反向标记:
- 首先遍历四条边界线,遇到'O'就启动DFS/BFS标记所有相连的'O'
- 这些被标记的'O'代表不会被转换的区域
- 最后遍历整个矩阵,将未标记的'O'转为'X',还原被标记的'O'
这种方法将O(mn)的时间复杂度优化为实际只需处理边界相关区域,大幅减少了不必要的计算。
3. 详细实现步骤
3.1 Python实现示例
python复制class Solution:
def solve(self, board: List[List[str]]) -> None:
if not board:
return
m, n = len(board), len(board[0])
# 标记与边界相连的O区域
def dfs(i, j):
if 0 <= i < m and 0 <= j < n and board[i][j] == 'O':
board[i][j] = 'T' # 临时标记
for x, y in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)]:
dfs(x, y)
# 处理四条边界
for i in range(m):
dfs(i, 0)
dfs(i, n-1)
for j in range(n):
dfs(0, j)
dfs(m-1, j)
# 转换矩阵
for i in range(m):
for j in range(n):
if board[i][j] == 'O':
board[i][j] = 'X'
elif board[i][j] == 'T':
board[i][j] = 'O'
3.2 关键参数说明
- 临时标记'T'的使用:避免直接修改'O'导致后续判断错误
- 递归终止条件:索引越界检查 + 当前值判断
- 方向数组:简化四个方向的遍历代码
- 边界处理顺序:先处理左右边界,再处理上下边界
4. 优化与变种思考
4.1 空间复杂度优化
原始解法使用了递归栈空间,可以改为迭代实现来优化:
python复制from collections import deque
def solve(board):
if not board:
return
m, n = len(board), len(board[0])
queue = deque()
# 边界O入队
for i in range(m):
for j in [0, n-1]:
if board[i][j] == 'O':
queue.append((i,j))
for j in range(n):
for i in [0, m-1]:
if board[i][j] == 'O':
queue.append((i,j))
# BFS标记
while queue:
i, j = queue.popleft()
if 0 <= i < m and 0 <= j < n and board[i][j] == 'O':
board[i][j] = 'T'
queue.extend([(i+1,j),(i-1,j),(i,j+1),(i,j-1)])
# 转换矩阵
for row in board:
for j in range(n):
if row[j] == 'O':
row[j] = 'X'
elif row[j] == 'T':
row[j] = 'O'
4.2 常见变种问题
- 计数问题:统计被转换的'O'数量
- 最大区域:找出最大的被转换区域
- 加权转换:不同区域的'O'有不同的转换规则
5. 实战注意事项
5.1 易错点排查
-
边界条件处理:
- 空矩阵检查
- 单行/单列矩阵的特殊情况
- 全'X'或全'O'矩阵
-
标记冲突:
- 确保临时标记不会与原有字符冲突
- 推荐使用Unicode中不常见的符号作为标记
-
方向遍历:
- 四个方向的偏移量要完整
- 避免重复访问导致的无限循环
5.2 测试用例设计
有效的测试矩阵应包括:
- 常规案例(有被包围和未被包围区域)
- 全'O'矩阵
- 全'X'矩阵
- 螺旋型'O'分布
- 大规模随机矩阵(测试性能)
示例测试用例:
python复制[
["X","X","X","X"],
["X","O","O","X"],
["X","X","O","X"],
["X","O","X","X"]
]
5.3 性能调优技巧
-
对于特别大的矩阵:
- 使用BFS替代DFS避免栈溢出
- 考虑分块处理或并行计算
- 使用位图压缩存储状态
-
多次查询场景:
- 预处理边界可达性信息
- 建立并查集数据结构快速判断连通性
-
内存优化:
- 原地修改矩阵,避免额外空间
- 使用位运算压缩状态标记
6. 同类问题扩展
掌握Surrounded Regions的解法后,可以解决以下类似问题:
- 岛屿数量问题(LeetCode 200)
- 矩阵中的最长递增路径(LeetCode 329)
- 被围绕的区域面积计算
- 二维矩阵的连通组件分析
- 围棋中的提子判断
这类矩阵遍历问题的核心在于:
- 正确设计标记策略
- 高效处理边界条件
- 选择适合的搜索方法(DFS/BFS)
- 优化空间复杂度
在实际面试中,面试官可能会要求解释算法选择的原因、分析时间/空间复杂度,甚至现场进行复杂度计算。建议在理解基础解法后,进一步思考各种优化可能性和权衡取舍。
