DailyChallenge
126. 单词接龙 II
Hard
20200607
Description
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回一个空列表。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
Solution
- 广度优先搜索(BFS):
while + queue
- 队列先进先出,尾部进,头部出
- 题解都在注释中
class Solution {
// 单词建图,广度优先搜索,BFS
// 单词到id的映射
// 看官方题解的,感觉如果有一个pair的数据结构不用这么麻烦
private Map<String, Integer> word2Id;
// id到单词的映射,下标对应id,直接用数组就可以了
private ArrayList<String> id2Word;
// 存图的边
private ArrayList<Integer>[] edges;
private static final int INF = 1 << 15;
public Solution(){
word2Id = new HashMap<>();
id2Word = new ArrayList<>();
}
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
// 建图
// 先设置顶点
int id = 0;
for(; id < wordList.size(); id++){
word2Id.put(wordList.get(id), id);
id2Word.add(wordList.get(id));
}
if(!word2Id.containsKey(beginWord)){
word2Id.put(beginWord, id);
id2Word.add(beginWord);
}
// 如果map没有endWord就没有解
if(!word2Id.containsKey(endWord)){
return new ArrayList<>();
}
// 处理边了
// 每个顶点要连接改变一个字母可达的位置,所以每个edge也是一个数组
// ArrayList(n),新建一个长度为n的ArrayList类型数组
edges = new ArrayList[id2Word.size()];
// 每一个edge[i]应该是一个数组,因为有不同数量相连的边
// 初始化一下
for(int i = 0; i < id2Word.size(); i++){
edges[i] = new ArrayList<>();
}
// 判断是否可达,初始化边
for(int i = 0; i < id2Word.size(); i++){
// 无向图,所以j = i + 1;
for(int j = i + 1; j < id2Word.size(); j++){
if(isArrive(id2Word.get(i), id2Word.get(j))){
edges[i].add(j);
edges[j].add(i);
}
}
}
int endId = word2Id.get(endWord);
// 答案
List<List<String>> res = new ArrayList<>();
// 求最短转换序列,所以添加cost变量,记录转化次数
// 到达每个点的cost
int[] cost = new int[id2Word.size()];
// 求最小,一般代价都要设置为最大值
for(int i = 0; i < id2Word.size(); i++){
cost[i] = INF;
}
// 广度优先搜索,就是queue+while的组合
// 先把起点加进去
Queue<ArrayList<Integer>> q = new LinkedList<>();
ArrayList<Integer> tmpBegin = new ArrayList<>();
tmpBegin.add(word2Id.get(beginWord));
q.add(tmpBegin);
cost[word2Id.get(beginWord)] = 0;
while(!q.isEmpty()){
// curr存的是id
ArrayList<Integer> curr = q.poll();
int last = curr.get(curr.size() - 1);
// 若为终点,则res加一个路径
if(last == endId){
ArrayList<String> ans = new ArrayList<>();
for(int c : curr){
// 由curr的路径id获得string,返回结果
ans.add(id2Word.get(c));
}
res.add(ans);
}else{
// 不为终点就继续找
for(int i = 0; i < edges[last].size(); i++){
int toId = edges[last].get(i);
// =是为了记录相同路径;< 因为cost初始化最大值;> 就说明之前来过,有更小的值
if(cost[last] + 1 <= cost[toId]){
cost[toId] = cost[last] + 1;
ArrayList<Integer> tmp = new ArrayList<Integer>(curr);
tmp.add(toId);
q.add(tmp);
}
}
}
}
return res;
}
public boolean isArrive(String a, String b){
int diff = 0;
for(int i = 0; i < a.length(); i++){
if(a.charAt(i) != b.charAt(i)){
diff++;
// if(diff > 1);
// return false;
}
}
return diff == 1;
}
}
欢迎关注微信公众号GitKid,每天分享LeetCode题解,让你走着看算法。