因为搜索所有答案,所有我有了个DFS的方案。但后来一看,超时又错误,因为看错题,求的是所有最短路径的解。
public class Solution { private ArrayList<ArrayList<String>> ans = new ArrayList<ArrayList<String>>(); private HashSet<String> visited = new HashSet<String>(); HashSet<String> dict = null; private String start = null; private String end = null; public ArrayList<ArrayList<String>> findLadders(String start, String end, HashSet<String> dict) { ans.clear(); visited.clear(); this.start = start; this.end = end; this.dict = dict; dict.add(start); dict.add(end); ArrayList<String> arr = new ArrayList<String>(); arr.add(start); sub(start, arr); return ans; } private void sub(String s, ArrayList<String> arr) { if (s.equals(end)) { ans.add(new ArrayList(arr)); } else { if (!dict.contains(s) || visited.contains(s)) { return; } visited.add(s); char[] ca = s.toCharArray(); for (int i = 0; i < s.length(); i++) { for (char c = 'a'; c <= 'z'; c++) { if (c != ca[i]) { char tmp = ca[i]; ca[i] = c; String nextS = new String(ca); arr.add(nextS); sub(nextS, arr); arr.remove(arr.size()-1); ca[i] = tmp; } } } visited.remove(s); } } }
然后修改优化,第一个想到的办法是DFS过程中记录最短路径,之后比该路径长的就不继续了,最后把结果集大于该路径的都删除。这个方法本来就有bad case,果然超时。
然后开始搜网上资料,和我一样,别人也想到用BFS。这样当搜到一个答案时,把这一层的全都做完就行了。而且,搜索时把之前层遍历到过的单词都放到visited集合里(本层的还不可以,因为比如"hot"在两个序列里都出现了),因为出现过的单词下次再出现肯定不是最短路了。经过先辈的试验,也光荣超时了。
参考:http://www.cnblogs.com/shawnhue/archive/2013/06/05/leetcode_126.html http://www.cnblogs.com/obama/archive/2013/08/08/3247095.html http://blog.csdn.net/snakeling/article/details/9105147
那么参考中说可以先建图,这样复杂性会大大下降,为什么呢?因为原来从start节点开始flood,每次展开都是26*L(L为单词长度)的可能性,这会指数级增长。现在是遍历字典里的词,每个单词尝试一下是否和能变换成其他单词,当词个数n很大时(远大于26*L),建图-邻接表-复杂度为O(n) [或O(26*L*n)]。(如果把字典中的单词两两比较是O(n^2))
建完图之后可以用BFS(其实就是图求最短路的过程),因为已经建好了邻接表,所以复杂度为O(n),因为每个单词入queue一次,且状态转移为O(1)。可以用前驱表来记录路径,比如prev[1] = [2, 3, 0]表示有三条路(2 to 1) 或 (3 to 1) 或 (0 to 1)。
如果还想优化,可以双向BFS,因为有起始节点和终结节点。
下面是代码,写的很痛苦,注释如下:
1.邻接表里存的是int类型的索引,i和j。
2.建邻接表的过程中,从集合转了个string的array,这样可以用index索引。
3.同时建立了一个index到string的map(中间变量),相当于反向查找表,方便查找。
4.在求最段路径中,建立了一个前驱表。同时存了一个距离数组,表示到该点的最短路径。
5.其实是一个求最短路径的算法,这里如果前驱表不为空,表示已经访问过了。
6.从前驱表建立最终结果也是一个递归的过程。
import java.util.*; public class Solution { private String start; private String end; private HashSet<String> dict; private ArrayList<ArrayList<String>> ans = new ArrayList<ArrayList<String>>(); private ArrayList<String> words = new ArrayList<String>(); public ArrayList<ArrayList<String>> findLadders(String start, String end, HashSet<String> dict) { ans.clear(); words.clear(); this.start = start; this.end = end; this.dict = dict; // wrong, start could be end dict.add(start); dict.add(end); for (String s : dict) { words.add(s); } // build adjacent list ArrayList<ArrayList<Integer>> adj = new ArrayList<ArrayList<Integer>>(); HashMap<String, Integer> ids = new HashMap<String, Integer>(); // the map from word to its id in array words for (int i = 0; i < words.size(); i++) { ids.put(words.get(i), i); adj.add(new ArrayList<Integer>()); } this.buildGraph(adj, ids); // find the start and end index int vs = 0; for (; !words.get(vs).equals(start); vs++); int ve = 0; for (; !words.get(ve).equals(end); ve++); // find the paths Queue<Integer> que = new LinkedList<Integer>(); int[] dist = new int[dict.size()]; // distance, distance[vs] = 0; for (int i = 0; i < dist.length; i++) { dist[i] = -1; } dist[vs] = 0; ArrayList<ArrayList<Integer>> prev = new ArrayList<ArrayList<Integer>>(); for (int i = 0; i < dict.size(); i++) { prev.add(new ArrayList<Integer>()); } for (int i : adj.get(vs)) { que.offer(i); dist[i] = 1; prev.get(i).add(vs); } while (que.size() != 0) { int idx = que.poll(); if (idx == ve) break; // the prev[ve] is already processed in previous iteration int d = dist[idx] + 1; for (int i : adj.get(idx)) { if (prev.get(i).size() == 0 && i != vs) // not visited { que.offer(i); prev.get(i).add(idx); dist[i] = d; } else if (dist[i] == d) // already visited, dist[i] should be <= d { prev.get(i).add(idx); } } } // generate the paths ArrayList<Integer> path = new ArrayList<Integer>(); genPath(prev, path, vs, ve); return ans; } private void genPath(ArrayList<ArrayList<Integer>> prev, ArrayList<Integer> path, int vs, int ve) { path.add(ve); if (ve == vs) { ArrayList<String> tmp = new ArrayList<String>(); for (int i = path.size()-1; i >=0; i--) { tmp.add(words.get(path.get(i))); } ans.add(tmp); } else { for (int i : prev.get(ve)) { genPath(prev, path, vs, i); } } path.remove(path.size()-1); } private void buildGraph(ArrayList<ArrayList<Integer>> adj, HashMap<String, Integer> ids) { for (int i = 0; i < words.size(); i++) { char[] ca = words.get(i).toCharArray(); for (int j = 0; j < words.get(0).length(); j++) { for (char c = 'a'; c <= 'z'; c++) { if (ca[j] == c) continue; char tmp = ca[j]; ca[j] = c; String s = new String(ca); if (dict.contains(s)) { adj.get(i).add(ids.get(s)); } ca[j] = tmp; } } } } }