携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第33天,点击查看活动详情
题目描述
字典 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的最短路径,每次转换只能有一个字母不同,且endWord必须存在在wordList中,否则返回0。
求从单词beginWord到单词endWord的最短转换序列,且除了beginWord之外都在wordList中,首先确定适用哪种解题方法。
查找最短路径可以联想到用广度优先来求解。
广度优先,每一次循环完一整层的可能,然后将当前层的下一层所有的可能性继续进行循环。因为在遍历过程中枚举的是所有可能遇见的可能性,所以如果存在解的话,一定会在最短路径上找到。
给定的条件是个平面条件,接下来就要思考怎么将它转换为可以用广度优先的思想进行答题的数据结构。即如何将当前给定的数据转成具有层次的图结构。
首先,假定beginWord长度为n,而beginWord和下一个单词之间只会有一个不同的字母,所以和beginWord有关联的单词有n-1个。
比如单词this,它下一个单词可能出现的组合模式为thi*、th*s、t*is、*his,所以这就构成了一个图数据。
依次类推,查找wordList中所有单词的组合结构,并且将这些能互相转换的单词放在一个map中,命名为nameRelatedMap。key为指定单词,value为当前key可能转换出来的单词列表;
比如上述例子中,形成的map中应该有以下元素:
this->[thi*,th*s,t*is,*his];
thi*->[this];
th*s->[this];
t*is->[this];
*his->[this];
初始化完nameRelatedMap,接下来就是如何从图数据中查找最短路径了。
创建一个数组queue,代表每次遍历的层;初始放入beginWord。创建一个记录当前遍历层数的索引index。
遍历当前元素,并从nameRelatedMap中取出对应的所有可转换的数据,然后再将它们放入queue中,继续下次循环。直到当前元素等于endWord或者队列内元素为空。
遍历时需要注意,已经进入过循环的不必再次放入queue中,所以可以添加一个数组,用来盛放已经遍历过的字符串。
我们称wordList中的字符串为真实字符串,由真实字符串延伸出来的字符串为虚拟字符串,即thi*等。
则在遍历时,要达到最终的字符串必定是这样的流程:
真实字符串-虚拟字符串-真实字符串-虚拟字符串-真实字符串。
所以经历的真实的字符串实际上为总层数的1/2,因为求的不是层数而是单词个数,所以需要+1加上最开始的那一个单词。
代码实现
public static int ladderLength(String beginWord, String endWord, List<String> wordList) {
// endWord一定在wordList中
if (!wordList.contains(endWord)) {
return 0;
}
HashMap<String, List<String>> nameRelatedMap = new HashMap<>();
for (String word : wordList) {
buildRelate(word,nameRelatedMap);
}
// 将beginWord放入map中
buildRelate(beginWord,nameRelatedMap);
// 进行循环
int index = 0;
ArrayList<String> loopedList = new ArrayList<>();
List<String> queue = new ArrayList<>();
queue.add(beginWord);
while (!queue.isEmpty()) {
List<String> newList = new ArrayList<>();
for (int i = 0; i < queue.size(); i++) {
String nowString = queue.get(i);
if (loopedList.contains(nowString)) {
continue;
}
loopedList.add(nowString);
// 如果当前元素等于endWord 直接返回
if (nowString.equals(endWord)) {
return index/2+1;
}
// 遍历当前所有元素
newList.addAll(nameRelatedMap.get(nowString));
}
index ++;
queue = newList;
}
return 0;
}
private static void buildRelate(String word, HashMap<String, List<String>> nameRelatedMap) {
char[] chars = word.toCharArray();
for (int i = 0; i < chars.length; i++) {
char temp = chars[i];
chars[i] = '*';
List<String> relateWords = nameRelatedMap.getOrDefault(word, new ArrayList<>());
String newString = new String(chars);
relateWords.add(newString);
nameRelatedMap.put(word,relateWords);
List<String> newRelateWords = nameRelatedMap.getOrDefault(newString, new ArrayList<>());
newRelateWords.add(word);
nameRelatedMap.put(newString,newRelateWords);
// 还原
chars[i] = temp;
}
}