题目:
Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:
1.Only one letter can be changed at a time
2.Each intermediate word must exist in the word list
For example,
Given:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Return
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
分析:
第一感觉是dfs + backtracking,就是先用bfs求出beginWord到endWord的最短距离,然后dfs + backtracking找到不同的路径,等于这个最短距离的留下,大于的pass,果断TLE。
简单思考了下,除了可能出现环的情况会TLE外,还可以通过剪枝的方法优化——用HashMap记录每个单词和beginWord之间的距离,当dfs遍历到某个单词已经比最短距离长的时候,就不用再遍历下去了。提交,又TLE。
仔细又一想,这种剪枝等于没剪枝,想想HashMap是怎么求出来的?是在BFS的时候层序更新的,搜索到endWord的时候就停止了。所以道理上不存在比到endWord更长的一条路。问题不是出在走了更长的路,而是因为所有的路线都是从beginWword辐射出去的,dfs搜索的话有太多的岔路。
所以一个比较自然的想法是,从endWord往回找,就可以避免许多岔路。进一步的剪枝是依据HashMap里记录的距离,规定dfs的方向只往里beginWord距离更小的方向遍历。
code:
import java.io.*;
import java.util.*;
public class WordLadderII {
public List> findLadders(String beginWord, String endWord, Set wordDict){
List> Ladders = new ArrayList>();
//优化一:BFS时记录每个单词的前驱
HashMap> preGraph = new HashMap>();
//优化二:记录每个节点的最短距离,往回找的时候可以进一步剪枝,同时在构图的过程中也避免了环的存在
HashMap distance = new HashMap();
wordDict.add(beginWord);
wordDict.add(endWord);
for(String word : wordDict){
preGraph.put(word, new ArrayList());
}
//build the graph, get the preGraph and distance
bfs(preGraph, distance, beginWord, wordDict);
//find all the shortest path
//List path = new ArrayList();
dfs(Ladders, new ArrayList(), endWord, beginWord, distance, preGraph);
return Ladders;
}
/*
*寻找差异为一的单词有两种方法:
*(1)将目标单词与字典里的所有单词比较,O(n*l), n是字典容量(1000+),l是单词长度;
*(2)将目标的字母用'a'-'z'挨个替换,利用Set.contains()函数搜索。由于Set.contains()是O(1)的,这种方法是O(26*l)
*/
public List getNextWords(String words, Set wordDict){
List nextWords = new ArrayList();
for(int i = 0; i < words.length(); i++){
for(char c = 'a'; c <= 'z'; c++){
if(c == words.charAt(i)) continue; //如果没有distance, 需要用这一步避免成环
char[] arr = words.toCharArray();
arr[i] = c;
String next = new String(arr);
if(wordDict.contains(next)) nextWords.add(next);
}
}
return nextWords;
}
public void bfs(HashMap> preGraph, HashMap distance, String beginWord, Set wordDict){
LinkedList queue = new LinkedList();
queue.offer(beginWord);
distance.put(beginWord, 0);
while(!queue.isEmpty()){
String curr = queue.poll();
for(String next : getNextWords(curr, wordDict)){
preGraph.get(next).add(curr);
if(!distance.containsKey(next)){
distance.put(next, distance.get(curr) + 1);
//保证每个单词之进入队列一次
queue.offer(next);
}
}
}
}
public void dfs(List> Ladders, List path, String curr, String beginWord, HashMap distance, HashMap> preGraph){
if(curr.equals(beginWord)){
path.add(curr);
Collections.reverse(path);
Ladders.add(path);
//Collections.reverse(path)
//path.remove(path.size() - 1)
return;
}
for(String next : preGraph.get(curr)){
//剪枝,去掉非最短路的情况
if(distance.get(curr) - 1 == distance.get(next)){
path.add(curr);
//如果这里直接用path而不是new ArrayList(path), 68行的代码就需要执行了
dfs(Ladders, new ArrayList(path), next, beginWord, distance, preGraph);
path.remove(path.size() - 1);
}
}
}
public static void main(String[] args){
Set dict = new HashSet();
dict.add("hot");
dict.add("dot");
dict.add("dog");
dict.add("lot");
dict.add("log");
WordLadderII WL = new WordLadderII();
List> ladders = WL.findLadders("hit","cog",dict);
for(List list : ladders){
System.out.println(list);
}
}
}
时间复杂度方面:
(1)bfs构图的过程中,每个点进入队列一次,时间是O(n);
(2)dfs找路线的时候最坏情况是O(n^2),平均时间复杂度是O(n).