题目介绍:LeetCode 79. Word Search

给定一个大小为 m×n 的字符网格 board 和一个字符串 word,判断 word 是否可以在网格中找到。leetcode

单词可以通过顺序相邻的格子中的字母来构成,相邻格子指水平或垂直相邻,同一个格子中的字母在同一次构造中不能被重复使用。leetcode

题目约束如下:leetcode

  • 1 ≤ m, n ≤ 6
  • 1 ≤ word.length ≤ 15
  • board 和 word 只包含大小写英文字母

基本思路:从暴力到 DFS

直观想法是:从每个格子出发,尝试走出一条长度为 word.length 的路径,然后把路径上经过的字符拼成字符串,最后与 word 做比较,如果相同则返回 true。这个思路的大方向是"从每个格子 DFS 搜索",但实现时有两个容易犯的错误:

  1. 先走满长度再比较整体字符串,导致大量无用搜索;
  2. 没有正确处理"一个格子只能用一次"的约束。leetcode

更合理的做法是用 DFS + 剪枝:

  1. 搜索过程中,当前位置的字符必须与 word[k] 相等,否则立刻剪枝返回 false;
  2. 匹配到 word 的最后一个字符时即可返回 true,无需再走多余的步数。leetcode

核心算法:递归 DFS + 回溯

整体框架可以概括为:外层枚举起点,内层用 DFS 向四个方向扩展。典型的 DFS 函数可以设计成:

参数 (i,j,k):当前在网格坐标 (i,j),准备匹配 word[k]。

终止条件:

  • 越界、当前位置已使用、字符不相等 → 直接返回 false。
  • 当 k == wordLen - 1 且字符匹配 → 找到完整单词,返回 true。

递归步骤:

  1. 先把当前格子标记为"已访问";
  2. 向上、下、左、右四个方向递归搜索下一个字符 k + 1;
  3. 如果任意方向返回 true,则整条链路返回 true;
  4. 若四个方向都失败,撤销当前格子的访问标记(回溯),返回 false。leetcode

外层逻辑则是:

  1. 对每个格子 (i,j) 调用 DFS,初始 k = 0;
  2. 只要有一次 DFS 返回 true,就可以立即返回 true;
  3. 如果所有起点都失败,返回 false。leetcode

visited 为什么要"回溯"

很多同学会有疑惑:图遍历题里也常用 DFS 和 visited,为什么平时好像不需要"回溯",而这题却必须恢复 visited?

关键在于:visited 的意义不同。

普通图遍历(如 Number of Islands 等)

  • 目标:把一个连通块内的所有节点都访问一遍。
  • visited 的含义:这个点在"整次搜索"里已经被访问过,不需要也不允许再次访问。
  • 特点:visited 是一个"全局状态",一旦置为 true 就不会再变回 false,自然没有回溯的过程。leetcode
  • 目标:找到一条符合条件的路径,路径是有顺序的。
  • visited 的含义:这个点在"当前这条路径"中已经使用过,不能在这条路径里重复使用,但换一条路径时可以重新使用。
  • 特点:visited 是"依赖于当前递归路径的状态",每深入一层多一个点被使用,每回退一层就要把这个点从当前路径的使用集合中移除,这就是"回溯"。leetcode

可以用一句话概括:

  • 遍历类 DFS:visited 表示"永久访问过",无需回退。
  • 回溯类 DFS:visited 表示"当前候选解里用过",随递归深度变化,需要回退。

Word Search 恰好属于第二类,所以需要"标记 → 递归 → 恢复"这一整套回溯流程。leetcode

复杂度与常见剪枝

在最坏情况下,如果不做任何剪枝,复杂度大致为:

  • 起点有 m⋅n 个;
  • 每一步最多 4 个方向;
  • 单词长度为 L;
  • 于是复杂度近似为 O(m⋅n⋅4^L)。leetcode

在当前题目给定的约束下(m,n≤6,L≤15),这种 DFS + 回溯是可以接受的,但仍可以做一些剪枝优化:

  1. 字符统计剪枝:预先统计棋盘中每个字符的出现次数,如果 word 某个字符的出现次数大于棋盘中的次数,可以直接返回 false。
  2. 搜索顺序剪枝:先从和 word[0] 相等的格子启动 DFS,可以少一些无效起点。
  3. 搜索时一旦字符不匹配 / 越界 / 已访问,立即返回 false,避免无用递归。leetcode

一个可行的 C 实现思路说明

以 C 为例,可以按如下结构组织代码(只做思路说明,不贴完整源码):leetcode

  1. 定义一个 visited 二维数组,和 board 的大小一致,用 0/1 表示未访问/已访问。
  2. exist 函数中:
    • 动态分配 visited;
    • 双重循环枚举起点 (i, j);
    • 对每个起点调用 dfs_visitor(board, boardSize, boardColSize, word, wordLen, 0, visited, i, j);
    • 一旦某次 DFS 返回 true,就可以记录结果并跳出循环;
    • 最后释放 visited,返回结果。
  3. dfs_visitor 中:
    • 先处理终止条件:索引越界、超出边界、已访问、字符不等等直接返回 false;
    • 如果 word_index == wordLen,说明前面所有字符已成功匹配,可返回 true;
    • 否则标记当前格子为已访问,向四个方向递归尝试 word_index + 1;
    • 任意方向成功则返回 true;
    • 四个方向都失败时,把当前格子恢复成未访问状态,然后返回 false。leetcode

这种实现就完整体现了"逐字符匹配 + visited + 回溯"的思想。

总结:什么时候要回溯

整理一下经验规律:

  1. 如果题目只是"遍历整张图/树一次",例如求连通块、统计数量,通常只需要一个全局的 visited,不需要恢复;
  2. 如果题目是在"从众多可能路径/组合里找出一个/多个解",例如 Word Search、组合/排列、N 皇后等,就属于回溯问题,需要在递归时对"当前选择"做标记,在返回时撤销标记,确保其它路径可以重新使用这些节点。

Word Search 是一个非常经典的"DFS + 回溯"入门题,弄清楚这个题中 visited 和回溯的作用,对后面做各类回溯题都会有很大帮助。

相关推荐
im_AMBER3 小时前
Leetcode 75 数对和 | 存在重复元素 II
c++·笔记·学习·算法·leetcode
leoufung3 小时前
LeetCode 427:Construct Quad Tree 题解与两种思路对比
算法·leetcode·职场和发展
sin_hielo3 小时前
leetcode 3531
数据结构·算法·leetcode
qy-ll3 小时前
Leetcode100题逐题详解
数据结构·python·学习·算法·leetcode
菜鸟233号3 小时前
力扣106 从中序与后序遍历序列构造二叉树 java实现
java·算法·leetcode
LYFlied4 小时前
LeetCode热题Top100:核心算法思想与前端实战套路
前端·算法·leetcode·面试·算法思想·算法套路·解题公式
程序员-King.4 小时前
day120—二分查找—统计公平数对的数目(LeetCode-2563)
算法·leetcode·二分查找·双指针
leoufung4 小时前
LeetCode 148:Sort List(链表排序)完整解析:从冒泡到归并
leetcode·链表·list
别学LeetCode4 小时前
#leetcode# 、
leetcode