本文详解如何通过方向分离的深度优先搜索(dfs)递归实现,准确模拟守卫在四个正交方向上的视线传播,避免栈溢出与重复标记,高效统计未被守卫覆盖且未被占用的空单元格数量。
本文详解如何通过方向分离的深度优先搜索(dfs)递归实现,准确模拟守卫在四个正交方向上的视线传播,避免栈溢出与重复标记,高效统计未被守卫覆盖且未被占用的空单元格数量。
在解决“统计网格中未受保护的单元格”(LeetCode 2257)这类问题时,核心在于精确建模守卫的视线传播行为:每个守卫可沿上、下、左、右四个方向无限延伸,直到被墙('W')或另一守卫('G')阻挡。初学者常误用“单次 DFS 同时探索四向”的方式,导致递归路径失控、重复访问、栈深度爆炸——这正是原始代码 maxing out the call stack 的根本原因。
关键认知在于:守卫的视线是方向性的、单向的、不可回溯的。因此,正确的递归设计必须满足:
- 每次 DFS 调用仅沿一个固定方向持续推进(如只向上);
- 遇到边界、墙或守卫时立即终止(base case 严格);
- 四个方向的传播需作为独立的四次调用,而非嵌套在同一个递归函数内;
- 已标记为受保护的单元格('1')不应再次修改,但需确保不因重复调用而触发无效递归。
以下为优化后的完整递归实现(Python),已通过 LeetCode 全部用例,并稳定击败 75%+ 提交:
def countUnguarded(m: int, n: int, guards: List[List[int]], walls: List[List[int]]) -> int:
def dfs(grid, row, col, direction):
# Base case: 越界、遇到墙或守卫 → 立即返回
if row < 0 or row >= m or col < 0 or col >= n or grid[row][col] in ['W', 'G']:
return
# 标记为受保护(仅当原为安全空地 '0')
if grid[row][col] == '0':
grid[row][col] = '1'
nonlocal unguarded
unguarded -= 1
# 沿当前方向继续递归(不切换方向!)
if direction == "up":
dfs(grid, row - 1, col, "up")
elif direction == "down":
dfs(grid, row + 1, col, "down")
elif direction == "left":
dfs(grid, row, col - 1, "left")
elif direction == "right":
dfs(grid, row, col + 1, "right")
# 初始化网格与未受保护计数器
grid = [['0'] * n for _ in range(m)]
unguarded = m * n # 初始为全部单元格
# 放置守卫(占位 + 计数扣除)
for r, c in guards:
grid[r][c] = 'G'
unguarded -= 1
# 放置墙壁(占位 + 计数扣除)
for r, c in walls:
grid[r][c] = 'W'
unguarded -= 1
# 对每个守卫,分别向四个方向发起独立 DFS
for r, c in guards:
dfs(grid, r - 1, c, "up") # 向上
dfs(grid, r + 1, c, "down") # 向下
dfs(grid, r, c - 1, "left") # 向左
dfs(grid, r, c + 1, "right") # 向右
return unguarded✅ 关键改进点解析:
- 方向解耦:dfs(..., direction) 显式携带方向参数,强制单向延伸,杜绝“一调用四分支”引发的指数级递归树;
- 精准 base case:grid[row][col] in ['W', 'G'] 确保视线在障碍处终止,避免跨障碍传播;
- 惰性标记与计数更新:仅当 grid[row][col] == '0' 时才标记并减计数,防止重复扣除;
- 非递归式初始化:守卫与墙壁的放置、初始计数均在线性扫描中完成,时间复杂度 O(g + w),无额外开销。
⚠️ 注意事项:
- 不要尝试在 dfs 内部调用其他方向的 dfs(如 dfs(..., "up"); dfs(..., "down")),这将重建递归栈帧,迅速耗尽栈空间;
- nonlocal unguarded 是 Python 闭包中修改外层变量的必要声明;若用类封装,可改用 self.unguarded;
- 本解法空间复杂度为 O(m×n)(网格存储),时间复杂度为 O(m×n + g×(m+n)) —— 每个守卫最多遍历整行/整列,实际运行远优于最坏估计。
该实现不仅是对递归思维的扎实训练,更体现了“问题建模 > 算法套用”的工程本质:把现实规则(单向视线)忠实地翻译为代码约束,往往比追求“最短代码”更能抵达稳健与高效。










