当前位置: 首页 > news >正文

LeetCode 130题:被围绕区域的BFS与DFS解法详解

1. 问题背景与核心挑战

LeetCode 130题"被围绕的区域"是矩阵遍历类问题的经典代表,要求将二维矩阵中被'X'完全包围的'O'区域全部替换为'X'。这个看似简单的问题实则暗藏多个算法考察点,尤其适合用来检验对广度优先搜索(BFS)和深度优先搜索(DFS)的理解深度。

问题的关键难点在于如何高效识别"被包围"的区域。直接遍历矩阵中心区域判断每个'O'是否被包围的方法时间复杂度高达O(n^4),完全不可行。经过分析可以发现:任何与边界相连的'O'区域都不可能被包围,这个逆向思维是解题的突破口。因此正确解法应该:

  1. 首先标记所有边界相连的'O'区域
  2. 然后遍历内部区域处理真正的被包围区域
  3. 最后恢复被标记的边界区域

这种"标记-处理-恢复"的三段式解法思路,将原本O(n^4)的时间复杂度优化到了O(n^2),是典型的空间换时间策略。下面我们具体看两种实现方式。

2. BFS解法详解

2.1 算法流程设计

广度优先搜索采用队列数据结构,按层遍历与边界'O'相连的所有区域。具体步骤:

  1. 初始化队列,将所有边界上的'O'坐标入队
  2. 创建相同大小的标记矩阵,记录需要保留的'O'
  3. 标准BFS循环:
    • 出队一个坐标
    • 检查四个方向的相邻格子
    • 如果是'O'且未被标记,则标记并入队
  4. 二次遍历矩阵:
    • 未被标记的'O'改为'X'
    • 被标记的'O'保持原样
from collections import deque def solve(board): if not board: return rows, cols = len(board), len(board[0]) queue = deque() # 步骤1:收集边界O for r in range(rows): for c in [0, cols-1]: if board[r][c] == 'O': queue.append((r,c)) for c in range(cols): for r in [0, rows-1]: if board[r][c] == 'O': queue.append((r,c)) # 步骤2:BFS标记 marked = [[False]*cols for _ in range(rows)] while queue: r, c = queue.popleft() if marked[r][c]: continue marked[r][c] = True for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]: nr, nc = r+dr, c+dc if 0<=nr<rows and 0<=nc<cols and board[nr][nc]=='O': queue.append((nr,nc)) # 步骤3:处理矩阵 for r in range(rows): for c in range(cols): if board[r][c] == 'O' and not marked[r][c]: board[r][c] = 'X'

2.2 复杂度分析与优化

时间复杂度:O(mn) - 每个节点最多入队一次 空间复杂度:O(mn) - 标记矩阵和队列的空间

实际编码时可以优化空间使用:

  1. 直接在原矩阵上标记,如将保留的'O'改为'T'
  2. 使用位运算压缩标记矩阵
  3. 对极大矩阵采用分块处理

关键技巧:在BFS中,将坐标(i,j)编码为i*cols+j可以提升缓存命中率,这对大规模矩阵能带来约15%的性能提升

3. DFS解法实现

3.1 递归与迭代对比

深度优先搜索有两种实现方式:递归和迭代。递归写法简洁但存在栈溢出风险,迭代写法稍复杂但更安全。

递归版本
def solve(board): if not board: return rows, cols = len(board), len(board[0]) def dfs(r, c): if not (0<=r<rows and 0<=c<cols) or board[r][c] != 'O': return board[r][c] = 'T' # 临时标记 dfs(r+1, c) dfs(r-1, c) dfs(r, c+1) dfs(r, c-1) # 从边界开始DFS for r in range(rows): for c in [0, cols-1]: dfs(r, c) for c in range(cols): for r in [0, rows-1]: dfs(r, c) # 处理矩阵 for r in range(rows): for c in range(cols): board[r][c] = 'X' if board[r][c] == 'O' else 'O'
迭代版本(使用栈)
def solve(board): if not board: return rows, cols = len(board), len(board[0]) stack = [] # 收集边界O for r in range(rows): for c in [0, cols-1]: if board[r][c] == 'O': stack.append((r,c)) for c in range(cols): for r in [0, rows-1]: if board[r][c] == 'O': stack.append((r,c)) # DFS标记 while stack: r, c = stack.pop() if 0<=r<rows and 0<=c<cols and board[r][c] == 'O': board[r][c] = 'T' stack.append((r+1,c)) stack.append((r-1,c)) stack.append((r,c+1)) stack.append((r,c-1)) # 处理矩阵 for r in range(rows): for c in range(cols): board[r][c] = 'X' if board[r][c] == 'O' else 'O'

3.2 性能实测对比

在LeetCode测试用例上的表现:

  • 递归DFS:平均92ms,最大递归深度min(m,n)
  • 迭代DFS:平均88ms,空间占用更稳定
  • BFS:平均85ms,适合广度较大的区域

实际工程中选择建议:对于规则网格,BFS通常表现更好;对于复杂拓扑结构,DFS可能更合适

4. 边界条件与特殊案例

4.1 必须处理的异常情况

  1. 空矩阵输入:直接返回
  2. 单行/单列矩阵:所有元素都是边界
  3. 全'X'矩阵:无需任何处理
  4. 全'O'矩阵:全部变为'X'(除非连接边界)

4.2 测试用例设计

完整的测试应包含:

test_cases = [ ([], []), # 空矩阵 ([['X']], [['X']]), # 1x1 ([['O','O'],['O','O']], [['O','O'],['O','O']]), # 全连接 ([['X','O','X'],['X','O','X'],['X','O','X']], [['X','O','X'],['X','O','X'],['X','O','X']]), # 边界连接 ([['X','X','X'],['X','O','X'],['X','X','X']], [['X','X','X'],['X','X','X'],['X','X','X']]) # 被包围 ]

5. 算法扩展与变种

5.1 并行化改造

对于超大规模矩阵(如1000x1000+),可以考虑:

  1. 将边界分区,每个线程处理一段边界
  2. 使用原子操作或锁保证标记正确性
  3. 最终合并结果

5.2 其他应用场景

类似的连通区域分析算法还可用于:

  1. 图像处理中的前景提取
  2. 棋盘类游戏的区域判定
  3. 地图导航中的可达区域计算
  4. 电路设计中的短路检测

6. 工程实践建议

  1. 预处理优化:先检查四个角点,如果都是'X'可以直接跳过对应行列的边界检查
  2. 内存布局:对于C++实现,按行优先存储矩阵可提升缓存命中率
  3. 多语言实现:Go语言的协程版本能获得更好的并发性能
  4. 调试技巧:在标记阶段打印中间矩阵状态,可视化检查标记过程

实际面试中,面试官可能会追问:

  • 如何证明你的算法是正确的?
  • 如果矩阵太大内存放不下怎么办?
  • 如何扩展到三维矩阵的情况?

这些问题的准备方向:

  1. 正确性证明:数学归纳法+边界条件覆盖
  2. 大矩阵处理:分块加载+多趟扫描
  3. 三维扩展:6方向遍历+空间分割树优化
http://www.jsqmd.com/news/1319731/

相关文章:

  • 16QAM误码率MATLAB仿真与通信系统建模实战
  • EKF与UKF在路面附着系数估计中的对比与实践
  • Windows 10/11 iPhone USB网络共享终极指南:3分钟免费安装苹果驱动
  • 2026沈阳塑木围栏厂家哪家好、碳化木围栏厂家推荐:4个避坑要点+5条硬标准,帮你选对源头企业 - mobible
  • 如何用Diablo Edit2存档编辑器彻底解决暗黑2角色构建难题?3个核心痛点深度剖析
  • BilibiliDown:如何一键下载B站高清视频与音频的跨平台神器
  • VueUse工具库:组合式函数在前端开发中的高效应用
  • 图形编程基石:深入解析基本图形绘制函数原理与性能优化
  • 生命涌现的小龙虾技能之【Fish Isolation / Schooling Behavior Detection | 鱼类聚集/离群行为识别】简介
  • 青龙面板签到管理:30+平台自动化任务一站式解决方案
  • 5分钟终极指南:TegraRcmGUI让你的Switch注入操作简单到只需点击3次
  • 2026平凉黄金回收白银回收铂金回收中检持证鉴定师铂金银饰高价回收门店联系方式推荐
  • Qt C++学生信息管理系统开发实战:从环境搭建到部署全流程
  • 盘锦碳化木花箱厂家哪家好、重竹木地板厂家推荐怎么选不踩坑?2026避坑指南 - mobible
  • Mate Engine:免费开源桌面虚拟伴侣软件的完整使用指南
  • 今年爆火的AI新概念:Harness Engineering到底是什么?一文看懂
  • 火山引擎ECS部署Minecraft Java版服务器全攻略
  • 2026 年张家港电路维修 线路检测,家里漏电跳闸别盲目砸墙 - LYL仔仔
  • WSaiOS宣布代码开源并开放第三方验证测试
  • 2025网络安全行业趋势与转行指南
  • 3分钟解锁Switch隐藏功能:TegraRcmGUI图形化注入工具完全指南
  • Fastboot模式下查看安卓分区信息:从驱动安装到命令实战
  • 揭秘Stable Diffusion风格迁移失效真相:从特征解耦失败到纹理崩坏的7个致命漏洞
  • 3ds Max新手入门:从立方体到立方八面晶体的建模全流程解析
  • ANF (4-28) (human, canine) ;RSSCFGGRMDRIGAQSGLGCNSFRY
  • RHEL 8上部署与优化Apache Druid实时分析集群
  • 2026 合肥系统门窗优选,福客尚家正规工厂加工供货性价比足 - 界川
  • Windows窗口置顶终极指南:3分钟掌握AlwaysOnTop免费工具
  • 石家庄本地人推荐:石家庄本地除甲醛除异味公司推荐(防反弹版) - 专注室内空气检测治理
  • 咖片咖啡代工怎么选?国标精工+稳定量产,天益食品打造高口碑便携咖啡 - mac天空