单词最短转换路径问题
题目描述:
给定两个单词beginWord和endWord,还有一本词典是list类型。
找到所有从beginWord变到endWord的最短转换路径,变动的规则是:
1,一次只能变一个位置的字符
2,每一个转换后的word一定要在list中
3,初始时list中没有beginWord这个词
示例:
输入: beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]
返回: [ [“hit”,”hot”,”dot”,”dog”,”cog”], [“hit”,”hot”,”lot”,”log”,”cog”] ]
注意:
1,返回值的类型为List(List(String))
2,如果不存在转化路径请返回空链表(不是null)
3,所有的词一定都是相同长度的
4,所有词都是小写的a~z
5,在list中没有重复的词
6,beginWord和endWord都不是空字符串或者null
题目难度:
题目思路:
1、先获取每个单词的临近节点,也就是每个单词换一个字母后是否在list词典中。具体方法:首先把beginWord字符串添加到字典中。遍历到当前节点时,则改变该单词每一个位置上的元素。然后判断新的单词是否在词典中出现,以及是否在set集合中。然后存入hashmap中,key就为这个单词,value就为这个单词的临近节点(list型)。这样遍历结束后,词典中每个单词的邻居结点都找到了。
2、记录每个单词到第一个单词的距离。此处记录的距离类似树的宽度优先遍历。距离每个单词到顶层的距离。
3、最后求最短转换路径并输出最短路径。知道每个单词的临近单词,以及每个单词到头结点的距离,就可以求所有走过的最短路径并输出。
代码实现:
import java.util.*;
/**
* Created by Zhaoyang Ge on 2018/9/7.
*/
/**
* 本题要求头单词到尾单词在字典中的最短路径
* 1、步骤一:要求每个单词的邻居结点,存到map中,key为改单词,value为list链表,该单词的所有邻居。
* 2、步骤二:要求出每个单词到头单词的距离。
* 3、步骤三:要求出头单词对应的每个最短转换路径,且最短转换路径可能不唯一。
*/
public class WordShortestConversionPath {
public static List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
wordList.add(beginWord);
HashMap<String, ArrayList<String>> nexts = getNexts(wordList); //得到所有单词的邻居结点存进map中
HashMap<String, Integer> distances = getDistances(beginWord, nexts); //得到每个单词到头结点的距离
LinkedList<String> pathList = new LinkedList<>();
List<List<String>> res = new ArrayList<>();
getShortestPath(beginWord, endWord, nexts, distances, pathList, res); //得到最短路径并输出最短路径
return res;
}
private static HashMap<String, ArrayList<String>> getNexts(List<String> words) {
HashSet<String> dict = new HashSet<>(words); //把每个单词放到set集合中去重,并得到每个单词的邻居结点
HashMap<String, ArrayList<String>> nexts = new HashMap<>();
for (int i = 0; i < words.size(); i++) {
nexts.put(words.get(i), getNext(words.get(i), dict)); //map中放着,每个单词及该单词的邻居结点
}
return nexts;
}
//求单个单词对应的邻居结点。之所以采用a-z的每个位置组合新单词,然后查看词典是否存在,这样时间复杂度为o(26N)
//而采用查到当前单词,然后遍历一下字典看看是否存在,这种时间复杂度为o(n^2),当词典很大,相对来说常数级别可以忽略
private static ArrayList<String> getNext(String word, HashSet<String> dict) {
ArrayList<String> res = new ArrayList<>();
char[] chs = word.toCharArray();
for (char cur = 'a'; cur <= 'z'; cur++) {//a-z依次与每个位置的值进行替换,组合成新的单词,并查看该单词在字典中是否存在
for (int i = 0; i < chs.length; i++) {
if (chs[i] != cur) {
char tmp = chs[i];
chs[i] = cur;
if (dict.contains(String.valueOf(chs))) {
res.add(String.valueOf(chs)); //该步骤会将word在词典中的所以邻居结点都添加到res链表中
}
chs[i] = tmp;
}
}
}
return res; //返回该单词对应的所有邻居结点
}
//要求每个单词到头结点的距离问题,其实就是宽度优先遍历问题,遍历到每一行,则距离+1
private static HashMap<String, Integer> getDistances(String beginWord, HashMap<String, ArrayList<String>> nexts) {
HashMap<String, Integer> distances = new HashMap<>();
distances.put(beginWord, 0);
Queue<String> queue = new LinkedList<>();
queue.add(beginWord);
HashSet<String> set = new HashSet<>();//若,上次某个节点查询完了,则接下来的邻居结点就不需要再查,如 a-b是邻居
// 则b-a就不需要算距离了
set.add(beginWord);
while (queue != null) {
String cur = queue.poll();
for (String str : nexts.get(cur)) { //当前节点的所有邻居结点
if (!set.contains(str)) {
set.add(str);
queue.add(str);
distances.put(str, distances.get(cur) + 1);
}
}
}
return distances;
}
//其实得到最短路径并输出该路径的问题,就是深度优先遍历的问题
private static void getShortestPath(String cur, String end, HashMap<String, ArrayList<String>> nexts,
HashMap<String, Integer> distances, LinkedList<String> solution,
List<List<String>> res) {
solution.add(cur);
if (end.equals(cur)) {
res.add(new LinkedList<>(solution)); //为每一次输出的最短转换路径添加到新的链表中
} else {
for (String next : nexts.get(cur)) {
if (distances.get(next) == distances.get(cur) + 1) {//只有满足下一个邻居结点是从上一个结点来的,才可能是最短距离
getShortestPath(next, end, nexts, distances, solution, res);
}
}
}
solution.pollLast(); //最后要把最后加进来的节点依次删除,是为了寻找其他的转换路径
}
}