Hard
题目描述
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格的序列 beginWord -> s1 -> s2 -> ... -> sk:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k时,每个si都在wordList中。注意,beginWord不需要在wordList中。 sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 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
解释:endWord "cog" 不在字典中,所以无法进行转换。
提示:
1 <= beginWord.length <= 10endWord.length == beginWord.length1 <= wordList.length <= 5000wordList[i].length == beginWord.lengthbeginWord、endWord和wordList[i]由小写英文字母组成beginWord != endWordwordList中的所有字符串 互不相同
解题思路
这是一个典型的最短路径问题,可以用广度优先搜索(BFS)来解决。
基本思路:
- 将问题转化为图的最短路径问题,每个单词是图中的一个节点
- 如果两个单词只有一个字母不同,则它们之间有一条边
- 使用 BFS 从
beginWord开始搜索,直到找到endWord
优化方法: 有两种常见的实现方式:
- 预构建图:预先构建所有单词之间的连接关系,然后进行 BFS
- 动态搜索:在 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) | 队列、访问集合和字典集合的空间开销 |
相关题目
. Word Ladder II (Hard)
. Minimum Genetic Mutation (Medium)