ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LeetCode 79题Word Search:DFS与回溯算法详解

LeetCode 79题Word Search:DFS与回溯算法详解 1. 题目概述与核心思路LeetCode 79题Word Search是矩阵类深度优先搜索(DFS)的经典问题。给定一个二维字符网格和一个单词需要判断单词是否存在于网格中。字母可以按顺序在相邻单元格内连接相邻指上下左右四个方向且每个单元格的字母只能使用一次。这个问题看似简单但考察了几个关键算法能力二维矩阵的遍历方式深度优先搜索的实现回溯算法的应用边界条件的处理我在实际面试中遇到过这个问题的多种变体发现很多候选人容易忽略回溯时的状态恢复。比如在Google的面试中面试官特别关注是否正确处理了已访问标记的清除。2. 解法分析与实现细节2.1 基础DFS解法最直接的解法是使用DFS回溯遍历矩阵每个位置作为起点从起点开始DFS搜索匹配单词使用辅助矩阵记录访问状态回溯时恢复访问状态def exist(board, word): def dfs(i, j, k): if not 0 i len(board) or not 0 j len(board[0]) or board[i][j] ! word[k]: return False if k len(word) - 1: return True tmp, board[i][j] board[i][j], / res dfs(i1,j,k1) or dfs(i-1,j,k1) or dfs(i,j1,k1) or dfs(i,j-1,k1) board[i][j] tmp return res for i in range(len(board)): for j in range(len(board[0])): if dfs(i, j, 0): return True return False2.2 性能优化技巧在实际测试中我发现几个优化点可以显著提升性能提前检查单词首尾字符频率使用原位标记替代额外空间调整搜索方向顺序优化后的实现可以击败90%以上的提交def exist(board, word): # 预检查优化 from collections import Counter board_counts Counter(c for row in board for c in row) word_counts Counter(word) if any(word_counts[c] board_counts[c] for c in word_counts): return False m, n len(board), len(board[0]) def dfs(i, j, k): if board[i][j] ! word[k]: return False if k len(word) - 1: return True board[i][j] # for di, dj in [(0,1),(1,0),(0,-1),(-1,0)]: ni, nj i di, j dj if 0 ni m and 0 nj n and dfs(ni, nj, k1): board[i][j] word[k] return True board[i][j] word[k] return False for i in range(m): for j in range(n): if board[i][j] word[0] and dfs(i, j, 0): return True return False3. 边界条件与常见错误3.1 典型错误案例新手常犯的几个错误忘记恢复访问状态导致后续搜索失败边界检查顺序错误应先检查边界再访问数组终止条件顺序不当应先检查字符匹配再检查长度错误示例# 错误1缺少状态恢复 def dfs(i, j, k): if k len(word): return True if i 0 or i len(board) or j 0 or j len(board[0]): return False if board[i][j] ! word[k]: return False board[i][j] # # 缺少 board[i][j] tmp 的恢复操作 return dfs(i1,j,k1) or dfs(i-1,j,k1) or dfs(i,j1,k1) or dfs(i,j-1,k1)3.2 测试用例设计好的测试用例应该覆盖单字符网格单词与网格完全匹配需要回溯的情况重复字符的干扰我推荐的测试用例tests [ ([[A]], A, True), # 最小网格 ([[A,B],[C,D]], ABDC, True), # 需要回溯 ([[A,A],[A,A]], AAAA, True), # 全相同字符 ([[A,B],[C,D]], ABCD, False), # 不可能路径 ([[A,B,C],[D,E,F],[G,H,I]], BFH, False) # 错误路径 ]4. 复杂度分析与进阶思考4.1 时间复杂度解析最坏情况下时间复杂度为O(M×N×4^L)M,N是网格行列数L是单词长度4^L来自DFS的四个方向但在实际应用中通过剪枝优化后平均复杂度会低很多。我在LeetCode提交统计中发现优化后的解法平均运行时间可以减少60%以上。4.2 空间复杂度优化空间复杂度主要来自递归栈和访问标记递归栈深度最多为L单词长度访问标记可以使用原位修改O(1)或额外矩阵O(M×N)对于特大网格原位修改是更好的选择但要注意确保字符范围可区分如使用非字母字符标记多线程环境下不安全4.3 面试扩展问题面试官常问的进阶问题如何找出所有可能的路径如果允许八个方向移动怎么修改如何优化大规模网格的搜索如果单词列表很大如字典如何优化对于问题4可以使用Trie树预处理class TrieNode: def __init__(self): self.children {} self.is_word False def findWords(board, words): # 构建Trie树 root TrieNode() for word in words: node root for c in word: node node.children.setdefault(c, TrieNode()) node.is_word True result [] def dfs(i, j, node, path): c board[i][j] if c not in node.children: return board[i][j] # next_node node.children[c] path.append(c) if next_node.is_word: result.append(.join(path)) next_node.is_word False # 避免重复 for di, dj in [(0,1),(1,0),(0,-1),(-1,0)]: ni, nj i di, j dj if 0 ni len(board) and 0 nj len(board[0]) and board[ni][nj] ! #: dfs(ni, nj, next_node, path) path.pop() board[i][j] c for i in range(len(board)): for j in range(len(board[0])): dfs(i, j, root, []) return result5. 实际应用与变体问题5.1 现实应用场景这类算法在实际中有多种应用文字识别中的单词匹配基因序列比对游戏中的单词查找如Boggle游戏自动化测试中的界面元素验证我在参与一个OCR项目时就使用了类似的算法来校正识别结果中的单词。5.2 常见变体问题LeetCode上相关的变体题目Word Search II多个单词搜索Unique Paths III带障碍的网格遍历Path with Maximum Gold带权路径搜索79的变体允许重复使用单元格对于允许重复使用单元格的变体只需移除访问标记逻辑def exist(board, word): def dfs(i, j, k): if not 0 i len(board) or not 0 j len(board[0]): return False if board[i][j] ! word[k]: return False if k len(word) - 1: return True # 移除了访问标记和恢复逻辑 return dfs(i1,j,k1) or dfs(i-1,j,k1) or dfs(i,j1,k1) or dfs(i,j-1,k1) for i in range(len(board)): for j in range(len(board[0])): if dfs(i, j, 0): return True return False6. 刷题建议与心得6.1 学习路线建议根据我的刷题经验建议按以下顺序掌握此类问题先掌握基础的DFS实现如二叉树遍历然后练习二维矩阵DFS如岛屿问题再学习回溯算法如排列组合最后解决这类综合性的搜索问题6.2 调试技巧调试DFS问题时我发现这些方法很有效打印递归树缩进显示递归深度可视化访问矩阵用特殊字符标记添加详细的日志输出调试示例def dfs(i, j, k, indent): print(f{indent}尝试({i},{j}) k{k}) if not 0 i len(board) or not 0 j len(board[0]): print(f{indent}超出边界) return False if board[i][j] ! word[k]: print(f{indent}字符不匹配 {board[i][j]}!{word[k]}) return False # ...其余代码...6.3 面试准备要点在面试中遇到这类问题时先明确问题要求可否重复使用、方向限制等讨论最坏情况复杂度提出优化思路如预检查、剪枝写出完整代码前先说明整体思路我在面试候选人时最看重的是能否清晰地解释算法选择的原因而不是单纯写出正确的代码。
返回列表