在问 · 算法题解

127. 单词接龙

困难图论单向广度优先搜索力扣原题 ↗

题目描述

输入起始词 beginWord、目标词 endWord 和单词表 wordList,所有单词长度相同。

从 beginWord 开始,每一步只改动当前单词的一个字母。每一步得到的单词都必须在 wordList 里,beginWord 本身可以不在。最后要得到 endWord。

返回最短变换序列中的单词个数,首尾两个单词都要计入。无法到达时返回 0。

示例 1
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5

解释:例如 hit -> hot -> dot -> dog -> cog,共 5 个单词。

示例 2
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0

解释:cog 不在单词表中,无法到达。

数据范围

  • 1 <= beginWord.length <= 10
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 5000
  • wordList[i].length == beginWord.length
  • 所有单词只含小写英文字母。
  • beginWord != endWord
  • wordList 中没有重复单词。

解题思路:单向广度优先搜索

把每个词看成图的节点,只差一个字母的两个词之间有边。求无权图的最短路,用 BFS。找邻居时不建图,而是枚举每个位置换成 26 个字母,再查集合。第一次弹出 endWord 时的步数就是答案。

为什么正确

队列中的 steps 单调不减;一个词在入队时就加入 seen,所以它的 steps 就是从起点到它的最短距离。

复杂度分析

时间 O(N · L · 26 · L),N 为词数,L 为词长;空间 O(N · L)。

易错点

要在入队时就标记 seen,而不是出队时。否则同一个词会被重复入队很多次,大数据会超时。另外 endWord 不在列表中时要直接返回 0。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        words = set(wordList)
        if endWord not in words:
            return 0
        queue = collections.deque([(beginWord, 1)])
        seen = {beginWord}
        while queue:
            word, steps = queue.popleft()
            if word == endWord:
                return steps
            for i in range(len(word)):
                for c in 'abcdefghijklmnopqrstuvwxyz':
                    nxt = word[:i] + c + word[i + 1:]
                    if nxt in words and nxt not in seen:
                        seen.add(nxt)
                        queue.append((nxt, steps + 1))
        return 0

载入编辑器,自己跑一遍 →

其他解法

单向广度优先搜索写法最直接;双向广度优先搜索从两端向中间搜索,分支多时明显更快。

解法 2:双向广度优先搜索 时间 O(N·L·Σ),空间 O(N·L)

从 beginWord 和 endWord 两端同时按层扩展,各用一个集合保存当前层。每轮只扩展较小的一侧,扩展出的新词从字典中删除。一旦新词出现在另一侧的集合中,说明两端相遇,返回当前步数加 1。

最坏复杂度不变,但搜索半径减半,实际访问的节点数往往少很多。需要维护两个集合并处理交换,写法更复杂。

from typing import List

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        words = set(wordList)
        if endWord not in words:
            return 0
        if beginWord == endWord:
            return 1
        front, back = {beginWord}, {endWord}
        words.discard(beginWord)
        words.discard(endWord)
        steps = 1
        while front and back:
            if len(front) > len(back):
                front, back = back, front
            nxt = set()
            for w in front:
                for i in range(len(w)):
                    for c in 'abcdefghijklmnopqrstuvwxyz':
                        nw = w[:i] + c + w[i + 1:]
                        if nw in back:
                            return steps + 1
                        if nw in words:
                            words.remove(nw)
                            nxt.add(nw)
            front = nxt
            steps += 1
        return 0

图论 专题的其他题目

查看 图论 全部题目 →