题目描述
思路解析
一句话答案:LeetCode 529 扫雷游戏:点到雷就改 X 结束;点到空格先数周围 8 格的雷,有雷填数字就停、没雷变 B 并递归揭开邻格,一次 DFS 铺开整片安全区,时间 O(m·n)。
点一下,盘面要变成什么样
给一张 m×n 的字符盘面 board:M 是没挖出的雷,E 是没挖出的空格,另给一次点击 click。点到雷(M)就改成 X、游戏结束;点到空格(E),先数它周围 8 格有几颗雷:有雷就改成对应数字、到此打住,一颗都没有就改成 B 并把周围没挖的格接着翻开,最后返回盘面。难点全在最后半句——一格空白会牵出一连串翻开,不是点哪翻哪。
为什么不能只把点中那格翻开
先只翻点中那一格看差在哪。若点到的空格周围没有雷,它要变 B,而 B 就意味着『邻格还得继续翻』,其中周围无雷的又会变 B、再往外带。只翻一格就停,等于把该展开的一大片安全区留在原地,盘面是错的。所以这题不是做一次判断,而是从点击点出发沿『无雷空格』一路蔓延,直到撞上贴着雷的格才收边——这种扎进去、走不通再退回来的遍历,就是深度优先搜索(DFS)。
空格的两条岔路,为什么只有 0 才扩散
把揭开一个空格拆成一把尺子:先数它周围 8 格(含 4 条对角线)的雷数 cnt。cnt 大于 0,说明紧挨着雷,填上 cnt 就停手——它是一道警戒线,再往外翻就踩进雷区。cnt 等于 0,周围一圈都安全,才标成 B、对没挖的邻格递归揭开。为什么偏偏 0 才扩散?因为数字格本身就在说『旁边有雷、别往这走』,点一下摊开一大片的边界,就是这些数字格圈出来的。
一次递归揭开,具体是怎么铺的
揭开某格 (i,j),用双层循环扫 x 从 i-1 到 i+1、y 从 j-1 到 j+1 这 9 个位置,每个先判越界(0≤x<m 且 0≤y<n),在界内且是 M 才计入 cnt。若 cnt 为 0,这圈里还没挖的空格(E)就递归揭开。循环也会扫到 (i,j) 自己,但它此刻是待处理的 E、不是 M,不会被误当雷,省了跳过自身的判断。一格一旦标成 B 或数字就不再是 E,不会二次揭开,递归也不打转。
拿一张 3×4 盘面点 (0,0) 走一遍
盘面 3 行 4 列,唯一的雷 M 在 (1,2),其余全是 E,点左上角 (0,0)。(0,0) 周围 (0,1)、(1,0)、(1,1) 都没雷,cnt=0,标 B 并把这三格排进递归。揭 (0,1):周围含 (1,2),数到 1 颗雷,填 1 停下。揭 (1,0):无雷变 B,带出 (1,1)、(2,0)、(2,1);(1,1) 挨着雷填 1,(2,0) 无雷变 B 又带出 (2,1),(2,1) 挨着雷填 1。最终最左一列是三个 B,紧邻一列三个 1,把那颗雷挡在右侧,共翻开 6 格。
复杂度,还有几处一漏就全盘皆错
每个格子最多被揭开一次,数周围 8 格是常数工作,时间 O(m·n);空间耗在递归栈,最坏整片盘面连成一块空白,栈深逼近格子总数,也是 O(m·n)。让结果全错的常是几处细节。空格不先数雷就变 B 往外扩,本该拦住洪水的数字墙没长出来,会冲穿整盘。数雷只顾上下左右 4 格漏了对角线,斜角的雷被算少,可相邻是含对角的 8 格。给填了数字的格再递归就越过警戒线,右侧不该翻的格也翻了。两种极端反而简单:空格周围无雷直接变 B,点下去是雷则一步改 X。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这把尺子:空格先数雷,有雷填数字就刹车,没雷变 B 接着冲。每一帧都在套它。
盘面总览 · 一颗雷藏在 (1,2):这是初始盘面。红色的 M 是雷,在 (1,2);其余灰色的 E 都是还没挖开的空格。我们要演两种点法。
规则一 · 假如点到的是雷:先看简单的一条。假如这次点的正好是 (1,2),翻开一看是 M,一颗雷。
规则一 · 变 X,游戏结束:碰到雷不数邻居、不扩散,直接把它改成 X,游戏立刻结束,返回盘面。这就是规则一,一步到位。
规则二 · 这次点空格 (0,0):重新开一局。这次点左上角 (0,0),它是空格,不是雷,于是从这里开始一次 DFS 揭开。
检查 (0,0) 的邻居:先把 (0,0) 周围在界内的邻居用橙色圈出来,一个一个看是不是雷,红色的才算一颗。
揭开 (0,0) · 数周围 8 格的雷:进入 (0,0)(紫色)。橙色圈出的是它在界内的邻居,挨个看一遍,数雷时只把标红的雷格算进去,这一格数到 0 颗雷。
变 B · 这格没有雷:周围一颗雷都没有,于是把 (0,0) 改成 B 空白(变蓝)。空白格意味着要继续往外揭开。
扩散 · 把没挖的邻居排进递归:(0,0) 是空白,把它周围还没挖的格 (0,1)、(1,0)、(1,1)(橙色)逐个压进递归栈,接着一个个揭开它们。
检查 (0,1) 的邻居:(0,1) 周围在界内的格子共 5 个,屏幕只圈出仍未揭开的 4 个候选格/雷;已经揭开的 (0,0) 不是雷、不影响这次数雷计数。
揭开 (0,1) · 数周围 8 格的雷:轮到 (0,1),同样数它周围 8 格,这一回数到 1 颗雷。
填数字 1 · 到此为止:周围雷数大于 0,所以 (0,1) 直接填成数字 1(变绿)。它像一道墙,挡住洪水,不再往外扩散。
揭开 (1,0) · 数周围 8 格的雷:轮到 (1,0),同样数它周围 8 格,这一回数到 0 颗雷。
变 B · 这格没有雷:(1,0) 周围同样没雷,也变成 B,继续向外冲。
扩散 · 把没挖的邻居排进递归:(1,0) 是空白,把它周围还没挖的格 (1,1)、(2,0)、(2,1)(橙色)逐个压进递归栈,接着一个个揭开它们。
揭开 (1,1) · 数周围 8 格的雷:轮到 (1,1),同样数它周围 8 格,这一回数到 1 颗雷。
填数字 1 · 到此为止:(1,1) 周围也有雷,填成 1 就刹车,这片墙又长出一格。
揭开 (2,0) · 数周围 8 格的雷:轮到 (2,0),同样数它周围 8 格,这一回数到 0 颗雷。
变 B · 这格没有雷:(2,0) 周围同样没雷,也变成 B,继续向外冲。
扩散 · 把没挖的邻居排进递归:(2,0) 是空白,把它周围还没挖的格 (2,1)(橙色)逐个压进递归栈,接着一个个揭开它们。
揭开 (2,1) · 数周围 8 格的雷:轮到 (2,1),同样数它周围 8 格,这一回数到 1 颗雷。
填数字 1 · 到此为止:(2,1) 周围也有雷,填成 1 就刹车,这片墙又长出一格。
揭开完毕 · 共翻开 6 格:DFS 全部返回。最左一列是三个 B 空白,旁边一列是三个数字 1 组成的墙,墙把右侧没必要揭开的格(含那颗雷 M)挡在外面,这就是最终返回的盘面。
边界先想清:单空格变 B、单雷变 X、贴着雷的空格填数字不扩散。
两个高频追问:DFS 与 BFS 揭开结果相同、深盘面用 BFS 防栈溢出;扫自身格无害。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = rightclass ListNode: def __init__(self, val=0, next=None): self.val = val self.next = nextclass Solution: def updateBoard(self, board: List[List[str]], click: List[int]) -> List[List[str]]: def dfs(i: int, j: int): cnt = 0 for x in range(i - 1, i + 2): for y in range(j - 1, j + 2): if 0 <= x < m and 0 <= y < n and board[x][y] == "M": cnt += 1 if cnt: board[i][j] = str(cnt) else: board[i][j] = "B" for x in range(i - 1, i + 2): for y in range(j - 1, j + 2): if 0 <= x < m and 0 <= y < n and board[x][y] == "E": dfs(x, y) m, n = len(board), len(board[0]) i, j = click if board[i][j] == "M": board[i][j] = "X" else: dfs(i, j) return board复杂度
- 时间:O(m·n),每个格子最多被揭开一次,揭开时数周围 8 格是常数工作
- 空间:O(m·n),最坏情况整片空白连成一块,递归栈深度可达格子总数
易错点
面试追问把动画讲成自己的话
追问用 DFS 和 BFS 做这题有区别吗?
追问数周围 8 格时,把自己这一格也算进循环要紧吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
数组嵌套
LeetCode 565 · 中等 · 沿着 数组套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题