Hard

题目描述

字典 wordList 中从单词 beginWordendWord转换序列 是一个按下述规格的序列 beginWord -> s1 -> s2 -> ... -> sk

  • 每一对相邻的单词只差一个字母。
  • 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

给你两个单词 beginWordendWord 和一个字典 wordList ,返回 beginWordendWord 的最短转换序列中的单词数目 。如果不存在这样的转换序列,返回 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
解释:endWord "cog" 不在字典中,所以无法进行转换。

提示:

  • 1 <= beginWord.length <= 10
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 5000
  • wordList[i].length == beginWord.length
  • beginWordendWordwordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有字符串 互不相同

解题思路

这是一个典型的最短路径问题,可以用广度优先搜索(BFS)来解决。

基本思路:

  1. 将问题转化为图的最短路径问题,每个单词是图中的一个节点
  2. 如果两个单词只有一个字母不同,则它们之间有一条边
  3. 使用 BFS 从 beginWord 开始搜索,直到找到 endWord

优化方法: 有两种常见的实现方式:

  1. 预构建图:预先构建所有单词之间的连接关系,然后进行 BFS
  2. 动态搜索:在 BFS 过程中动态寻找相邻单词

推荐解法:动态搜索 + 哈希集合优化

  • 使用哈希集合存储 wordList,快速判断单词是否存在
  • 对于当前单词,尝试修改每个位置的字母(a-z),检查是否在字典中
  • 使用 visited 集合避免重复访问
  • BFS 保证找到的是最短路径

双向 BFS 优化: 可以从起点和终点同时开始搜索,当两个搜索相遇时停止,这样可以显著减少搜索空间。

时间复杂度主要取决于单词长度和字典大小,空间复杂度为存储访问状态所需的空间。

代码实现

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> wordSet(wordList.begin(), wordList.end());
        if (wordSet.find(endWord) == wordSet.end()) {
            return 0;
        }
        
        queue<string> q;
        unordered_set<string> visited;
        q.push(beginWord);
        visited.insert(beginWord);
        
        int level = 1;
        
        while (!q.empty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                string word = q.front();
                q.pop();
                
                if (word == endWord) {
                    return level;
                }
                
                for (int j = 0; j < word.length(); j++) {
                    char original = word[j];
                    for (char c = 'a'; c <= 'z'; c++) {
                        if (c == original) continue;
                        word[j] = c;
                        
                        if (wordSet.count(word) && !visited.count(word)) {
                            q.push(word);
                            visited.insert(word);
                        }
                    }
                    word[j] = original;
                }
            }
            level++;
        }
        
        return 0;
    }
};
class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        if endWord not in wordList:
            return 0
        
        wordSet = set(wordList)
        queue = collections.deque([beginWord])
        visited = set([beginWord])
        level = 1
        
        while queue:
            for _ in range(len(queue)):
                word = queue.popleft()
                
                if word == endWord:
                    return level
                
                for i in range(len(word)):
                    for c in 'abcdefghijklmnopqrstuvwxyz':
                        if c == word[i]:
                            continue
                        
                        new_word = word[:i] + c + word[i+1:]
                        
                        if new_word in wordSet and new_word not in visited:
                            queue.append(new_word)
                            visited.add(new_word)
            
            level += 1
        
        return 0
public class Solution {
    public int LadderLength(string beginWord, string endWord, IList<string> wordList) {
        var wordSet = new HashSet<string>(wordList);
        if (!wordSet.Contains(endWord)) {
            return 0;
        }
        
        var queue = new Queue<string>();
        var visited = new HashSet<string>();
        queue.Enqueue(beginWord);
        visited.Add(beginWord);
        
        int level = 1;
        
        while (queue.Count > 0) {
            int size = queue.Count;
            for (int i = 0; i < size; i++) {
                string word = queue.Dequeue();
                
                if (word == endWord) {
                    return level;
                }
                
                char[] wordArray = word.ToCharArray();
                for (int j = 0; j < wordArray.Length; j++) {
                    char original = wordArray[j];
                    for (char c = 'a'; c <= 'z'; c++) {
                        if (c == original) continue;
                        wordArray[j] = c;
                        string newWord = new string(wordArray);
                        
                        if (wordSet.Contains(newWord) && !visited.Contains(newWord)) {
                            queue.Enqueue(newWord);
                            visited.Add(newWord);
                        }
                    }
                    wordArray[j] = original;
                }
            }
            level++;
        }
        
        return 0;
    }
}
var ladderLength = function(beginWord, endWord, wordList) {
    if (!wordList.includes(endWord)) return 0;
    
    const wordSet = new Set(wordList);
    const queue = [[beginWord, 1]];
    const visited = new Set([beginWord]);
    
    while (queue.length > 0) {
        const [word, length] = queue.shift();
        
        if (word === endWord) return length;
        
        for (let i = 0; i < word.length; i++) {
            for (let c = 97; c <= 122; c++) {
                const char = String.fromCharCode(c);
                if (char === word[i]) continue;
                
                const newWord = word.slice(0, i) + char + word.slice(i + 1);
                
                if (wordSet.has(newWord) && !visited.has(newWord)) {
                    visited.add(newWord);
                    queue.push([newWord, length + 1]);
                }
            }
        }
    }
    
    return 0;
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(M² × N)M 是单词长度,N 是字典中单词数量。对于每个单词,需要尝试 M × 26 种变化
空间复杂度O(N)队列、访问集合和字典集合的空间开销

相关题目