题目描述
判断 word 能否在网格里相邻连续拼出:每一步只能走上下左右,且同一格不能重复用。
思路解析
最直接的想法:拿每个格子当起点暴力试。但只要不把「正在走的路径」标记成用过,就会从 A 走到 B、又从 B 退回 A,原地来回、甚至无限绕——根本停不下来。
关键招是回溯:进入 (i,j) 先把它改成占位符 #(占住,四向递归就不会再踩它),等这一格的所有方向都探完,再把它改回原字母。这样别的起点、别的方向还能复用这格。匹配当前字符 → 占住 → 四向找下一个字符 → 撤销,是这题的主循环。
起点 (0,0) = A:扫格子找单词首字母:(0,0)=A 和 word[0]='A' 对上,从这里开始 DFS,目标是接着找 word[1]='B'。
占住 A · 四向找 B:把 (0,0) 临时改成 # 占住(灰)。看它四周找 'B':上、左越界,下方 (1,0)=S 不是 B,右边 (0,1)=B 对上了,走过去。
占住 B · 准备找 F:把 (0,1)=B 也占住(灰)。前两个字符 'AB' 已经拼上,现在要在 B 的四个邻居里找 word[2]='F',按「上右下左」顺序逐个试。
先试右邻 (0,2)=E:B 上方越界,先看右邻 (0,2)=E,递归进去比一比:它等于要找的 'F' 吗?
负例:E ≠ F → 这个方向不通:负例:(0,2)=E,不等于要找的 'F',这一格直接失败返回,不会占住它、也不会往下递归。回到 B,继续试 B 的下一个方向。
换方向:B 的下方 (1,1)=F:E 不通,换试 B 下方 (1,1)=F——正好是要找的 'F'!走过去。这一步就是「走不通就回退、换下一个方向」。
匹配完成 → true:(1,1)=F 是 word 的最后一个字符,k 已经到末尾,整条路径 A→B→F 拼全 → 返回 true。(若这格也没拼下去,就会一路回退、把沿途 # 改回原字母,换起点重来。)
回溯:撤销标记还原网格:收尾看清「撤销」这步:递归往回退时,沿途的 (1,1)、(0,1)、(0,0) 依次把 # 改回 F、B、A,网格恢复原样。这样换别的起点搜索时,这些格子还是干净可用的。
凡是「在网格里搜一条满足条件的路径」都是网格回溯:进格子先占住防重复,走不通就撤销标记、退回换方向。迷宫、最长递增路径、岛屿连通都是它的变体。
参考代码(Python)
1def exist(board, word):
2 m, n = len(board), len(board[0])
3 def dfs(i, j, k): # k: 正在匹配 word 的第几个字符
4 if board[i][j] != word[k]: return False # 当前格不匹配,剪枝
5 if k == len(word) - 1: return True # 最后一个字符也对上 → 成功
6 tmp, board[i][j] = board[i][j], class="cl-str">"#" # 占住:临时标记用过
7 for di, dj in ((-1,0),(0,1),(1,0),(0,-1)):# 上右下左四个方向
8 ni, nj = i + di, j + dj
9 if 0 <= ni < m and 0 <= nj < n and board[ni][nj] != class="cl-str">"#":
10 if dfs(ni, nj, k + 1): return True # 某方向走通即可
11 board[i][j] = tmp # 撤销标记(回溯)
12 return False
13 return any(dfs(i, j, 0) for i in range(m) for j in range(n)) # 每格当起点复杂度分析
- 时间复杂度:O(m·n·4^L) —— m·n 个起点,每步最多 4 个方向,深度 L
- 空间复杂度:O(L) —— 递归栈深度 = 单词长度 L
套路模板
骨架:失败/成功先判、占住、四向递归、撤销标记。「占住」和「撤销」必须成对出现——这是网格回溯不出错的关键。
1def dfs(i, j, 状态):
2 if 当前格失败: return False # 剪枝
3 if 到达成功条件: return True
4 标记(i, j) = 占位符 # 占住
5 for ni, nj in 四个相邻方向:
6 if 不越界 and 未被占住:
7 if dfs(ni, nj, 新状态): return True
8 还原(i, j) = 原值 # 撤销标记(回溯)
9 return False易错点
- 错误写法:走进格子不标记用过 → 正确写法:进入就临时占住(改成 #)(不占住会从 A 走到 B 又退回 A,原地来回、把同一格重复用,永远停不下来)
- 错误写法:递归后忘了把 # 改回原字母 → 正确写法:四向探完后恢复原值(不撤销会污染别的起点的搜索,让本能拼出的单词被误判为找不到)
- 错误写法:先递归再判越界/匹配 → 正确写法:先判越界与是否匹配,再决定递归(顺序反了会下标越界报错,或拿越界格去比字符)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。