79. 单词搜索

中等 含交互动画

学完这道你应该能: 说清它为什么用「网格回溯」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

判断 word 能否在网格里相邻连续拼出:每一步只能走上下左右,且同一格不能重复用

board = A B E / S F C / A D E
word = "ABF"
输出 = true

思路解析

最直接的想法:拿每个格子当起点暴力试。但只要不把「正在走的路径」标记成用过,就会从 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)

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

套路模板

骨架:失败/成功先判、占住、四向递归、撤销标记。「占住」和「撤销」必须成对出现——这是网格回溯不出错的关键。

Python
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,原地来回、把同一格重复用,永远停不下来)
  • 错误写法:递归后忘了把 # 改回原字母正确写法:四向探完后恢复原值(不撤销会污染别的起点的搜索,让本能拼出的单词被误判为找不到)
  • 错误写法:先递归再判越界/匹配正确写法:先判越界与是否匹配,再决定递归(顺序反了会下标越界报错,或拿越界格去比字符)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →217. 存在重复元素 ← 返回题库