← 返回题目列表

单词接龙为什么用 BFS 求最短转换序列?

高频 困难 第 15 / 30 题 更新于 2026/07/30
BFS最短路径字符串建图

简化版

单词接龙把每个单词看成图节点,两个单词只差一个字符就连边。题目要求最少转换次数,所以用 BFS 按层扩展;第一次到达 endWord 的层数就是最短长度。

详细版

朴素做法是每次枚举当前单词的每个位置,把字符替换成 a-z,如果新单词在字典中且没访问过,就入队。BFS 队列里存单词和步数,起点步数为 1。为了避免重复访问,要把入队后的单词从字典或 visited 中标记掉。

优化做法是预处理通配模式,例如 hot 生成 *ot、h*t、ho*,同模式下的单词互为邻居。这样可以减少反复构造邻居的成本。复杂度通常按单词数 N、单词长度 L 分析,通配建图约 O(NL)

完整版教学

一、为什么这是图最短路

每个合法单词是一个节点,如果两个单词长度相同且只有一个字符不同,就可以一步转换,也就是一条无权边。要求从 beginWordendWord 的最少转换次数,本质就是无权图最短路径。

hit -> hot -> dot -> dog -> cog
           \-> lot -> log -> cog

所有边的成本都是 1,所以 BFS 是天然选择。Dijkstra 也能求无权最短路,但多余;DFS 会先走深路径,不保证第一次找到的是最短。

二、BFS 层数代表转换长度

BFS 一层一层扩展,第一层是起点,第二层是一步可达单词,第三层是两步可达单词。第一次遇到终点时,当前层数就是最短转换序列长度。

层数单词例子含义
1hit起点
2hot转换 1 次
3dot, lot转换 2 次
4dog, log转换 3 次
5cog转换 4 次,序列长度 5

注意题目常返回的是序列中单词数量,不是边数,所以起点层数通常记为 1。如果问转换次数,则答案应是层数减 1。

三、枚举邻居的直接写法

直接写法不需要预建完整图。对当前单词的每个位置,尝试替换成 26 个字母,形成新单词,如果在字典里就是邻居。

int ladderLength(String begin, String end, List<String> wordList) {
    Set<String> dict = new HashSet<>(wordList);
    if (!dict.contains(end)) return 0;
    Queue<String> q = new ArrayDeque<>();
    q.offer(begin);
    int steps = 1;
    while (!q.isEmpty()) {
        for (int size = q.size(); size > 0; size--) {
            String cur = q.poll();
            if (cur.equals(end)) return steps;
            char[] arr = cur.toCharArray();
            for (int i = 0; i < arr.length; i++) {
                char old = arr[i];
                for (char ch = 'a'; ch <= 'z'; ch++) {
                    arr[i] = ch;
                    String next = new String(arr);
                    if (dict.remove(next)) q.offer(next);
                }
                arr[i] = old;
            }
        }
        steps++;
    }
    return 0;
}

这里用 dict.remove(next) 同时完成“判断存在”和“标记访问”。入队时就删除,能避免同一层或后续层重复入队。

四、通配模式如何优化

通配模式把“只差一个字符”的关系转成中间桶。例如 hot 的模式是 *ot、h*t、ho*dotlot 都在 *ot 桶里,所以它们都是 hot 的邻居。

*ot -> hot, dot, lot
d*g -> dog, dug
ho* -> hot, hog

预处理 N 个单词,每个单词生成 L 个模式,总共 O(NL) 个插入。BFS 时通过当前单词的 L 个模式找到候选邻居,避免每次做 26L 次字符串构造。在词表很大时,这种方式更常见。

五、双向 BFS 为什么更快

普通 BFS 从起点向外扩展,分支多时节点数增长很快。双向 BFS 同时从 begin 和 end 两端扩展,每次扩展较小的一侧,直到两边相遇。

单向: b^d
双向: b^(d/2) + b^(d/2)

如果分支因子 b=10、最短深度 d=6,单向大约可能扩到 10^6 量级,双向两边各扩到 10^3 量级,差距明显。面试中先写普通 BFS,再说明双向 BFS 优化,会比较稳。

六、边界条件与复杂度

如果 endWord 不在词表里,经典题直接返回 0。beginWord 不一定在词表中,但可以作为 BFS 起点。所有单词长度通常相同,否则“一次改变一个字符”的建边规则不成立。

写法时间复杂度粗略口径空间
枚举 26 字母O(N * L * 26) 到 O(N * L * 26 * 字符串成本)O(N)
通配模式O(NL + BFS 邻居扫描)O(NL)
双向 BFS最坏仍可能很高,平均显著减少搜索O(N)

复杂度口径不必死抠常数,重点是说明每个单词尽量只入队一次,visited 是避免爆炸的关键。

七、常见误区与追问

记忆钩子:单词接龙是“无权图最短路”,看到最少转换次数就先想到 BFS 层序。

  • 误区:用 DFS 找到终点就返回。 DFS 第一次找到的路径不一定最短,除非额外做全局最小搜索,代价很高。
  • 误区:访问标记在出队时才做。 同一个单词可能被同一层多个父节点重复入队,通常入队时就标记。
  • 误区:返回转换边数而不是序列长度。 经典题返回包含 begin 和 end 的单词数量,起点层数应为 1。
  • 追问:为什么 endWord 不在词表返回 0? 因为每次转换后的中间词和终点都必须来自词表,终点不存在就无法到达。
  • 追问:如何输出所有最短路径? 需要 BFS 建父节点关系,并只保留最短层,再回溯生成路径。
  • 追问:如何优化大词表? 用通配模式或双向 BFS,减少邻居枚举和搜索层数。

八、加强记忆

单词接龙的关键是建模:单词是节点,一次改一个字符是无权边,最少转换就是 BFS 最短路。普通 BFS 要在入队时标记访问,层数从 1 开始。词表大时,用通配桶快速找邻居;搜索深时,用双向 BFS 缩小扩展范围。