[Leetcode 126] 单词接龙 II

type
status
date
summary
tags
category
icon
password
featured
freq
difficulty
给定两个单词 beginWordendWord,以及一个字典 wordList 。
从单词beginWord转化到单词endWord,转化序列为beginWord -> s1 -> s2 -> ... -> sk,需要满足:
  • 每对相邻的单词之间仅有单个字母不同。
  • 转换过程中的每个单词 si1 <= i <= k)必须是字典 wordList 中的单词。 其中beginWord 不必是字典wordList 中的单词,sk == endWord
返回所有从beginWord转化到endWord最短转换序列,如果不存在这样的转换序列,返回一个空列表。
notion image
 
示例 1:
示例 2:
说明:
  • 1 <= beginWord.length <= 5
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 500
  • wordList[i].length == beginWord.length
  • beginWordendWord 和 wordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有单词 互不相同
 

解法一:BFS + DFS

  1. BFS 建距离表 + 记录前驱
      • beginWord 开始进行 BFS;
      • dist[word] 记录从起点到该单词的层数;
      • prev[word] 记录可以通向该单词的“前驱单词”(上一层的节点);
      • 一旦找到 endWord,说明已经到达最短层,可以停止 BFS。
  1. DFS 回溯所有路径
      • endWord 反向追溯;
      • 每次通过 prev[word] 找到所有可能的上一步;
      • 递归展开所有路径,直到回到起点。
最终我们得到所有的最短路径组合。
复杂度
  • 时间:O(N × L²)
    • N 是单词数量,L 是单词长度;
    • 每次 BFS 需要替换 L 个位置、尝试 26 个字母;
    • 实际平均会低于理论上限,因为单词过滤后减少。
  • 空间:O(N × L)
    • 主要用于字典映射、prev 记录前驱关系以及队列。

解法二:预处理邻接表 + BFS

上面的写法是边搜索边生成相邻单词,也可以提前把所有相邻关系存好。
对输入的单词做预处理:给每一个单词的每一位分别变成一个下划线。
例如 hot 变成: - _ot - h_t - ho_
这样可以很轻易地找到从每一个单词可以换到哪些单词。 如 hot_ot 映射下能找到 lotdot
这种方式适合字典很大的场景,因为只构建一次邻接表。
 
具体做法:
  1. 遍历字典,按 mask 模式(_ot, h_t, ho_)分组;
  1. BFS用于找到最短转换路径,可以直接通过 mask 找所有邻居;
  1. 同样记录 prev 表;
  1. 最后 DFS 回溯所有路径。
复杂度
  • 时间:O(N × L²)
    • N 为字典长度,L 为单词长度;
    • 每个单词生成 L 个 mask;
    • BFS 访问每个单词一次。
  • 空间:O(N × L)
    • 存储 mask 映射和 prev 关系。

解法三:双向 BFS + 回溯(最优解)

BFS 的瓶颈在于搜索空间太大,而我们知道起点和终点。
双向 BFS 可以从两端同时扩展,可以显著减半搜索层数
  1. 从两边出发:begin_setend_set
  1. 每次扩展较小的一边;
  1. 记录相邻关系;
  1. 当两边相遇时,说明找到最短路径;
  1. 最后仍然 DFS 回溯所有路径。
复杂度
  • 时间:O(N × L),比单向 BFS 更快;
  • 空间:O(N × L)
 
上一篇
[Leetcode 124] 二叉树中的最大路径和
下一篇
[Leetcode 128] 最长连续序列