给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 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” 不在字典中,所以无法进行转换。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
方法一:广度优先搜索法(参考官方答案)
思路:每个单词如果逐个替换其中一个字母为’’,就可以将每个单词替换得到的新单词和原单词对应起来,但是有可能存在重复情况,例如:dog,dig都可以替换得到dg,所以对应关系原单词应该用链表存放,然后就开始拿着startWord去替换,和存放所以可能的map对比,相等则取出临近单词列表,继续比对,记住所有单只能使用一次,做标记,每走一步则步数加一,直到找到临近列表中存在endWord,则说明已经找到结果,返回步数即可;
步骤:
1,先将wordList中的所有单词替换,找到所有临近节点链表,对应起来,用Map all存放;
2,使用队列,将新单词和初始步数1进入队列,然后取出队列头第一个键值对,键是当前单词,值是当前步数,如果该键值存在all里面,说明当前单词存在临近单词,也就是只替换一个字母就能得到的单词,然后判断all里面这个键值对应的list中是否存在endWord,如果存在则说明,当前单词只需要在转换一个字母即可得到endWord,返回结果当前步数加一即可;如果不存在,则判断这个列表中的单词是否已被使用,使用则加入标记数组,否则就将该单词和步数加一放入队列进行下一轮判断;
3,直到队列为空则结束,如果不包括则返回0;
方法二:双向BFS
思路:由于方法一,需要逐个判断,如果每个单词对应的替换情况多,数据量太大,需要优化;方法一是从startWord开始,每次如果有临近单词列表,则逐个判断,不考虑列表内容多少;现在优化为双向开始,也就是判断是startWord对应的临近单词列表大还是endWord大,那个小则从那个开始判断,其实每次还是单向搜索,只是有了选择性,都是从省力的一方开始;
步骤:
1,需要三个HashSet,一个存放当前startWord 对应的临近单词set start,一个存放endWord对应的临近单词set end,还有一个为all,存放所有单词列表,步数初始值为2,因为已经各自走了一步;
2,归根结底每次都还是从start 中拿单词进行判断查找临近列表,所有如果start为空时,说明已经遍历完,如果end比start大,则互换,让end成为start;
3,将start中的每个单词拿出来,(每个单词只能使用一次,每次遍历start时记得要将all的所有start中的元素删除;)逐个替换每个字母从a - z,判断其是否存在于all中,存在则向当前单词的临近列表temp中添加,如果此时end中也存在,则说明只需要再走一步即是结果;
4,当前start遍历完成后,如果没有找到结果,将temp变成start,步数加一,继续进行下一轮直到找到结果!
import javafx.util.Pair;
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// return ladder1(beginWord, endWord, wordList);
return ladder2(beginWord, endWord, wordList);
}
//广度优先搜素
private int ladder1(String beginWord, String endWord, List<String> wordList) {
//特殊情况
if (wordList == null || wordList.size() == 0)
return 0;
if (!wordList.contains(endWord))
return 0;
//将wordListzhong 每个单词所能替代的所有情况列出来
int L = beginWord.length();
Map<String, List<String>> all = new HashMap<>();
wordList.forEach(word -> {
//将每个单词对应情况填入hashmap中
for (int i = 0; i < L; i ++) {
String newWord = word.substring(0,i) + '*' + word.substring(i + 1, L);
//存在一对多
List<String> trans = all.getOrDefault(newWord, new ArrayList<String>());
trans.add(word);
all.put(newWord, trans);
}
});
//使用队列进行BFS
Queue<Pair<String,Integer>> q = new LinkedList<Pair<String, Integer>>();
//防止对同一个单词重复操作
List<String> check = new ArrayList<String>();
//刚开始为1
q.add(new Pair(beginWord,1));
while(!q.isEmpty()) {
Pair<String,Integer> node = q.remove();//取出头
String word = node.getKey();
int level = node.getValue();
//找出当前word能匹配到的currenWord
for (int i = 0; i < L; i ++) {
//如果匹配到,并且这个单词对应的元单词列表中存在endWord,则结果就是level+1;
String newWord = word.substring(0,i) + '*' + word.substring(i+1,L);
//判断newWord是否是all中的一个键
for (String adWord : all.getOrDefault(newWord, new ArrayList<String>())) {
//列表存在endWord,则结束
if (adWord.equals(endWord))
return level + 1;
//如果该单词未被使用则放入队列,否则跳过
if (!check.contains(adWord)) {
check.add(adWord);
q.add(new Pair(adWord,level + 1));
}
}
}
}
return 0;
}
//双向广度优先搜索,两边同时搜索
private int ladder2(String beginWord, String endWord, List<String> wordList) {
//特殊情况
if (wordList == null || wordList.size() == 0)
return 0;
if (!wordList.contains(endWord))
return 0;
//防止重复,使用hashset;
Set<String> start = new HashSet<>();
Set<String> end = new HashSet<>();
//存放所有单词
Set<String> all = new HashSet<>(wordList);
start.add(beginWord);
end.add(endWord);
//双向搜索,所以初始步数即为2
return bfs(start, end, all, 2);
}
//双向BFS,谁近走谁的路线,每次替换一个字母
//递归
private int bfs(Set<String> start, Set<String> end, Set<String> all, int l) {
//结束条件,出现断裂,找不到合适的路径走下一步,则结束
if (start.size() == 0)
return 0;
//那一端可能性少从那端出发
if (start.size() > end.size())
return bfs(end, start, all, l);
//其实还是从一端进行搜索而已,只是每次都从少的一段开始,不重复使用,用了就移除
all.removeAll(start);
//每次都是改变一个字母然后搜索所有临近点
Set<String> temp = new HashSet<>();
for (String s : start) {
//变化每个字母,注意记录原值,要回档
char[] arr = s.toCharArray();
for (int i = 0; i < arr.length; i ++) {
char tmp = arr[i];
//重复跳过
for (char c = 'a'; c <= 'z'; c ++) {
if (c == tmp)
continue;
arr[i] = c;
String str = new String(arr);
//如果该单词存在于源列表,判断
if (all.contains(str)) {
//如果结果集中也有,则说明找到
if (end.contains(str))
return l;
//否则加入临近列表
temp.add(str);
}
arr[i] = tmp;
}
}
}
//start变为temp开始下一轮
return bfs(temp, end, all, l + 1);
}
}