news 2026/3/14 13:50:15

【每日算法】LeetCode 79. 单词搜索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【每日算法】LeetCode 79. 单词搜索

对前端开发者而言,学习算法绝非为了“炫技”。它是你从“页面构建者”迈向“复杂系统设计者”的关键阶梯。它将你的编码能力从“实现功能”提升到“设计优雅、高效解决方案”的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 79. 单词搜索

1. 题目描述

给定一个m x n的二维字符网格board和一个字符串单词word。要求找出单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用

示例 1:
输入:board =[["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word ="ABCCED"
输出:true
解释:在网格中可以找到一条路径A->B->C->C->E->D

示例 2:
输入:board =[["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word ="SEE"
输出:true

示例 3:
输入:board =[["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word ="ABCB"
输出:false

2. 问题分析

这是一个典型的二维平面上的搜索(遍历)问题,需要在有限的、有约束的空间内寻找一条特定路径。其核心特征包括:

  1. 搜索空间m x n的二维网格。
  2. 约束条件
    • 路径必须连续(上下左右四个方向)。
    • 路径不能重复使用同一个网格单元。
    • 路径的字符序列必须完全匹配目标单词。
  3. 目标:判断是否存在至少一条满足条件的路径。

从前端视角看,这类问题类似于:

  • 可视化搭建平台:检查用户绘制的连线图是否能形成某个特定序列。
  • 游戏逻辑:如“一笔画”、“寻找单词”等小游戏的底层判断逻辑。
  • DOM 树或虚拟 DOM 的特定节点序列查找:在复杂的树形结构中,寻找符合特定规则的节点路径。

3. 解题思路(给出复杂度并指出最优解)

面对此类“在约束条件下寻找所有可能路径”的问题,深度优先搜索(DFS)配合回溯是直觉且主流的解法。这也是本题的最优解,因为我们需要探索“一条路走到黑”的可能性,并在失败时“回头”尝试其他岔路。

核心思路(回溯算法)

  1. 起点:遍历网格中的每一个单元格(i, j),将其作为搜索的起点。
  2. DFS 递归搜索:从当前单元格出发,向上下左右四个方向进行递归探索。
  3. 递归定义:定义函数dfs(i, j, k),表示从网格(i, j)位置开始,能否匹配到单词word从第k个字符开始的后缀。
  4. 递归三部曲
    • 终止条件
      • k === word.length:说明已经成功匹配整个单词,返回true
      • ij越界,或board[i][j] !== word[k],或当前单元格已被访问:当前路径失败,返回false
    • 处理当前层:标记当前单元格(i, j)为“已访问”,防止在本轮搜索中重复使用。
    • 下探到下一层:向四个方向(i+1, j),(i-1, j),(i, j+1),(i, j-1)发起递归调用dfs(nextI, nextJ, k+1)
    • 回溯清理这是关键步骤。在从当前单元格返回之前,撤销对其的“已访问”标记,以便其他搜索路径可以正常使用该单元格。
  5. 结果整合:如果任何起点的 DFS 返回true,则最终结果为true;否则为false

为什么这是最优解?

  • 该算法本质上是穷举所有可能的路径,但由于引入了“访问标记”和及时剪枝(字符不匹配立即返回),避免了大量的无效搜索。
  • 其时间复杂度在 worst-case 下虽为指数级,但对于此类 NP 难问题的子集,回溯是已知最有效的精确解法。
  • 空间复杂度主要取决于递归调用栈的深度,最大为单词长度L,是可控的。

4. 各思路代码实现 (JavaScript)

4.1 思路一:DFS + 回溯(最优解实现)

/** * @param {character[][]} board * @param {string} word * @return {boolean} */varexist=function(board,word){constm=board.length;constn=board[0].length;constwordLen=word.length;// 方向数组,代表上下左右四个方向的偏移量constdirections=[[0,1],[0,-1],[1,0],[-1,0]];// 用于标记单元格是否在当前搜索路径中被访问过constvisited=newArray(m).fill(0).map(()=>newArray(n).fill(false));/** * DFS 回溯函数 * @param {number} i - 当前行索引 * @param {number} j - 当前列索引 * @param {number} k - 当前匹配到单词的第几个字符 (0-indexed) * @return {boolean} */constdfs=(i,j,k)=>{// 1. 递归终止条件(失败)if(i<0||i>=m||j<0||j>=n||visited[i][j]||board[i][j]!==word[k]){returnfalse;}// 2. 递归终止条件(成功)if(k===wordLen-1){returntrue;}// 3. 处理当前层:标记为已访问visited[i][j]=true;// 4. 递归下探到下一层(四个方向)for(const[dx,dy]ofdirections){constnewI=i+dx;constnewJ=j+dy;// 如果任意一个方向能找到剩余部分,立即返回true,不再探索其他方向if(dfs(newI,newJ,k+1)){// 注意:这里返回前也需要清理visited?不,因为成功找到了,整个调用栈会依次返回true,visited的状态不再重要。// 但为了逻辑清晰和 visited 数组的复用,可以在最终返回前统一清理,或者在成功路径返回前也清理。// 更常见的做法是:在最终成功返回true前,先清理当前节点的状态,确保函数退出时状态是干净的。// 我们调整一下代码结构,将清理放在 finally 位置。visited[i][j]=false;// 回溯,清理当前节点状态returntrue;}}// 5. 四个方向都走不通,回溯:撤销当前节点的访问标记visited[i][j]=false;returnfalse;};// 主循环:尝试每一个单元格作为起点for(leti=0;i<m;i++){for(letj=0;j<n;j++){// 优化:如果起点字符就不匹配,直接跳过if(board[i][j]===word[0]){if(dfs(i,j,0)){returntrue;}}}}returnfalse;};

优化技巧(代码中已体现)

  1. 方向数组:使代码更简洁,避免写四个类似的递归调用。
  2. 起点字符预判:在调用dfs前,先判断起点字符是否匹配单词首字符,减少不必要的递归入口。
  3. 回溯状态的及时清理:确保递归函数返回后,visited状态恢复到进入前的样子,这是回溯算法的精髓。

5. 各实现思路的复杂度、优缺点对比表格

特性DFS + 回溯 (上述实现)备注
时间复杂度O(m * n * 3^L)最坏情况下,每个起点都要探索,每个节点(除了第一步)有3个方向可走(不能走回头路),探索深度为单词长度 L。这是一个理论上界。
空间复杂度O(L + m * n)O(L)为递归栈的最大深度;O(m*n)visited辅助矩阵的开销(可优化至 O(L),见下文)。
优点1. 思路直观,符合人的思维方式。
2. 剪枝及时,实际运行效率在一般数据下可接受。
3. 是解决此类约束性路径搜索的标准且最优方法。
缺点1. 最坏情况时间复杂度高,在网格大、单词长时可能超时(但LeetCode测试用例通常规避了最坏情况)。
2. 需要额外的visited空间。
空间优化点可以使用原地修改board 的方式省去visited矩阵:将访问过的单元格暂时修改为一个不可能出现在 word 中的字符(如#\0),递归返回前再改回来。这样空间复杂度可降为O(L)优化后代码片段
javascript<br>const temp = board[i][j];<br>board[i][j] = '#'; // 标记已访问<br>// ... 递归 ...<br>board[i][j] = temp; // 回溯恢复<br>

6. 总结

LeetCode 79. 单词搜索是一道经典的回溯算法入门题,它清晰地展示了DFS在探索所有可能性、遇到障碍时回退的完整流程。

对前端开发者的核心价值

  1. 算法思维训练:强化“递归+回溯”的思维模式。这种模式在前端处理树形结构操作(如虚拟DOM的diff、目录树的展开/收起、权限路由的递归检查)、状态空间搜索(如表单配置的多步骤验证、工作流审批路径)时非常有用。
  2. 解决实际问题
    • 在线文档/表格:实现类似“公式追踪”功能,查找某个单元格的值是如何由其他单元格计算而来的路径。
    • 可视化搭建/低代码平台:检查用户拖拽连接的组件节点是否能形成一条有效的逻辑链。
    • 游戏开发:开发网页版的“Boggle”(找单词)或“数独”类游戏,核心逻辑就是此类网格搜索。
  3. 性能意识提升:通过分析该算法的时间复杂度,你会更深刻地理解为什么某些DOM操作(如深度嵌套的递归查询)或大规模数据遍历会成为性能瓶颈,从而在设计阶段就考虑优化方案。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 19:58:35

九章算Adv. Mater.解读【水凝胶】中山大学附属第五医院/华南理工大学:按压密封水凝胶贴片,实现深度切口的快速止血与修复

【文章信息】通讯作者&#xff1a;中山大学附属第五医院彭欣副研究员、华南理工大学边黎明教授第一作者&#xff1a;中山大学附属第五医院2022级联培博士研究生袁康瑞共同第一作者&#xff1a;中山大学附属第五医院2023级硕士研究生何川东该成果得到了国家自然科学基金项目与中…

作者头像 李华
网站建设 2026/3/9 13:27:32

研究生该如何看文献?——带着三个层次的问题看文献

看文献的时候要带着问题看文献&#xff0c;不同阶段问题不一样。 第一层次问题&#xff0c;是什么&#xff1f; 刚入组的新生&#xff0c;包括研究生和本科生&#xff0c;刚开始接触一个研究方向&#xff0c;主要问题是弄清楚这个研究是什么&#xff1f; 包括这篇论文做了哪…

作者头像 李华
网站建设 2026/3/13 21:09:34

32、深入掌握 Bash 脚本中的条件判断与逻辑控制

深入掌握 Bash 脚本中的条件判断与逻辑控制 在 Bash 脚本编程中,条件判断和逻辑控制是非常重要的部分,它们能够让脚本根据不同的情况做出不同的响应。下面将详细介绍相关的命令和表达式。 1. test 命令的使用 在 if 语句中, test 命令是最常用的。它有两种等效形式:…

作者头像 李华