题目链接: leetcode.cn/problems/wo…
题目描述:
字典 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 (这两个单词的长度是一样的,并且给定的时候是不相等的),我们需要将起始单词通过替换字母的方式,转换为 endWord,而且不是随意变换的,每次只能换一个字母,替换完的单词还必须出现在给定的词库中,然后重复此过程。最终找到替换次数最少的方式,并返回替换的单词数目(包含beginWord)。 如果把这题转换一下:从起始点出发,每次走一格,请找到最短到达终点的路径,并返回步数。 熟悉了吧,其实大差不差。
直接举例
如上图,hot 继续往下 BFS 我们还是可以有很多的变换方法,但是替换完字母单词都不在库中(cot、hog),所以只能舍弃,而能使用的就是 dot 和 lot 这两个单词了(绿色分支),他们都在库中。我们继续使用这俩个单词往下 BFS。
dot 此时怎么变化库中都不含有它的单词(当然是排除再次变化为 hot ,此方法直接排除掉,待会讲细节会讲到),
所以只剩下 lot 这一个分支了,发现可以变化为 log 最后变化为 cog。一共是五个单词(包含 hit 本身),返回 5。
这时候问题就来了,我们替换的字母是怎么来的?从 a - z 随便选的吗?其实从 a 遍历到 z 也可以,但是不是最优解。我们变化后的单词必须包含在库中,那么我们直接将单词库中所含的字母拿出来,作为一个字母库,使用字母库就可以了,库中都没有的字母,你组成的单词库中自然也不含有。
还有处理重复出现过的单词,我们修改过的单词就不要在进行重复回滚了,就比如 dot 可以回滚为 hot,但是这样毫无意义,因为这条路绕一个圈之后肯定是笔 hot-lot-log 更长的,直接舍弃掉就可以了。直接使用 Set 来存储已经修改过的单词(删除重复的值)。 还有一个细节,就是我们在判断库中是否含有这个单词的时候,是需要遍历的,我们不如直接使用 Set 然后使用 contains 方法来判断 下面我们来总结一下此题目的所有步骤:
- 获取到库中的单词
- 获取到库中的所有字母
- 创建一个库,用于存储修改过的单词(判断是否重复)
- 创建 queue 队列,将 beginWord 存储到其中,等候BFS
- 创建 step 用于记录修改过字母的个数
- 开始BFS
- 返回 下面就开始写代码
代码
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
//存储所有单词
Set<String> words = new HashSet<>();
//存储所有字母
Set<Character> alphabet = new HashSet<>();
//判断是否修改过
Set<String> isExist = new HashSet<>();
//拿到所有的单词和字母
for (String str : wordList) {
//拿到单词
words.add(str);
char[] cur = str.toCharArray();
for (char tmp : cur) {
//拿到字母
alphabet.add(tmp);
}
}
//如果单词中不含有endWord的话直接返回0,不含有endword就说明无解了
if (!words.contains(endWord)) return 0;
//创建queue
Queue<String> queue = new LinkedList<>();
queue.add(beginWord);
//创建单词个数(因为包含beginWord本身,所以初始化为1)
int step = 1;
//开始BFS
while (!queue.isEmpty()) {
//步数++
step++;
//拿到所有分支
int size = queue.size();
//遍历所有分支
while (size-- != 0) {
String word = queue.poll();
for (int i = 0 ; i < word.length(); i++) {
//开始替换字母
char[] charWord = word.toCharArray();
for (char c : alphabet) {
//替换字母
charWord[i] = c;
//将字符数组转换为字符串
String newWord = new String(charWord);
//判断修改过的单词是否等于endWord,是的话直接返回步数
if (newWord.equals(endWord)) return step;
//判断是否在库中且是否使用过这个单词
if (words.contains(newWord) && !isExist.contains(newWord)) {
//没有使用过且在库中的话就将他们放到队列中(找到分支)再放到使用过的库中
queue.add(newWord);
isExist.add(newWord);
}
}
}
}
}
//走完BFS说明没有结果,直接返回0
return 0;
}
}