一、题目描述
字典 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 <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord
、endWord
和wordList[i]
由小写英文字母组成beginWord != endWord
wordList
中的所有字符串 互不相同
二、解题思路
-
将单词列表转换为图 :首先,我们需要构建一个图,其中包含
wordList
中的所有单词。这可以通过比较每对单词并检查它们是否只有一个字符不同来实现。 -
广度优先搜索 :从
beginWord
开始,进行广度优先搜索,寻找endWord
。在搜索过程中,我们记录从beginWord
到当前单词的路径长度。 -
终止条件 :一旦我们找到了
endWord
,我们就知道我们已经找到了最短的转换序列,并且可以返回当前的路径长度。 -
优化 :为了提高搜索效率,我们可以在开始 BFS 之前预处理
wordList
,创建一个通用状态映射,这样我们可以快速找到所有与给定单词只有一个字符差异的单词。
三、具体代码
java
import java.util.*;
public class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// 将单词列表转换为集合,便于快速查找
Set<String> wordSet = new HashSet<>(wordList);
if (!wordSet.contains(endWord)) return 0;
// BFS 队列
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord);
// 记录已访问的单词
Set<String> visited = new HashSet<>();
visited.add(beginWord);
// 记录转换序列的长度
int level = 1;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String currentWord = queue.poll();
// 如果找到了结束单词
if (currentWord.equals(endWord)) {
return level;
}
// 遍历当前单词的所有可能转换
for (String nextWord : getNextWords(currentWord, wordSet)) {
if (!visited.contains(nextWord)) {
queue.offer(nextWord);
visited.add(nextWord);
}
}
}
level++;
}
return 0;
}
// 获取当前单词的所有可能转换
private List<String> getNextWords(String word, Set<String> wordSet) {
List<String> nextWords = new ArrayList<>();
for (char ch = 'a'; ch <= 'z'; ch++) {
for (int i = 0; i < word.length(); i++) {
// 生成下一个可能的单词
String nextWord = replace(word, i, ch);
if (wordSet.contains(nextWord)) {
nextWords.add(nextWord);
}
}
}
return nextWords;
}
// 替换单词中的某个字符
private String replace(String word, int index, char ch) {
char[] chars = word.toCharArray();
chars[index] = ch;
return new String(chars);
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
-
构建
wordSet
:将wordList
转换为HashSet
的时间复杂度是 O(N),其中 N 是wordList
的长度。 -
BFS 遍历:在 BFS 过程中,每个单词都可能生成 26 * L 个新单词,其中 L 是单词的长度。在最坏的情况下,所有单词都可能被访问一次。因此,时间复杂度是 O(N * 26 * L)。
-
getNextWords
函数:对于每个单词,我们检查 26 * L 个可能的转换。这个操作的时间复杂度是 O(26 * L)。 -
replace
函数:这个函数的时间复杂度是 O(L),因为我们只是替换了一个字符并生成了一个新的字符串。 -
综上所述,总的时间复杂度是 O(N * 26 * L)。
2. 空间复杂度
-
wordSet
和visited
:这两个HashSet
最多存储 N 个单词,因此它们的空间复杂度是 O(N)。 -
队列
queue
:在 BFS 过程中,队列中最多可能存储 N 个单词,因此空间复杂度是 O(N)。 -
nextWords
列表 :在getNextWords
函数中,这个列表最多可能存储 26 * L 个单词,因此空间复杂度是 O(26 * L)。 -
其他变量 :如
level
和循环中的临时变量等,它们的空间复杂度是 O(1)。 -
综上所述,总的空间复杂度是 O(N + 26 * L)。
综合来看,这个算法的时间复杂度是 O(N * 26 * L),空间复杂度是 O(N + 26 * L)。
五、总结知识点
-
集合的使用 :
HashSet
用于存储不重复的元素,并提供了快速的查找、添加和删除操作。代码中使用了HashSet
来存储wordList
和已访问的单词。 -
队列的使用 :
LinkedList
作为队列使用,实现了先进先出的数据结构。在 BFS 过程中,队列用于存储待访问的单词。 -
广度优先搜索(BFS) :这是一种图遍历算法,用于在图中找到最短路径。代码中使用了 BFS 来找到从
beginWord
到endWord
的最短转换序列。 -
字符串操作 :
replace
函数展示了如何通过索引替换字符串中的字符。这涉及到字符串转字符数组,然后修改数组中的元素,最后将数组转回字符串。 -
循环和条件语句 :代码中使用了
for
循环来遍历单词的每个字符和字母表中的每个字符,以及if
语句来检查条件。 -
函数定义和调用 :代码中定义了
ladderLength
、getNextWords
和replace
三个函数,分别用于解决不同的问题。函数的定义和调用展示了模块化编程的思想。 -
字符和字符数组 :代码中使用了字符变量
ch
来遍历字母表,并使用了字符数组chars
来表示和操作字符串。 -
数据结构的选择 :代码中选择合适的数据结构(如
HashSet
和Queue
)来优化算法的性能,特别是在单词查找和图遍历方面。 -
算法设计:代码实现了基于 BFS 的图遍历算法,这是一种常见的算法设计技巧,用于解决最短路径问题。
-
递归和迭代:虽然代码中没有直接使用递归,但 BFS 本身是一种递归思想的迭代实现,每次迭代都相当于展开递归的一层。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。