题目:
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
思路:先建立dict的邻接链表,例子中的结果如下:
hit : [hot]
cog : [dog, log]
hot : [hit, lot, dot]
lot : [hot, log, dot]
dog : [cog, log, dot]
log : [cog, lot, dog]
dot : [lot, hot, dog]
在Word LadderI的基础上,由于要重新构建结果集,因此要自己创建一个Node类,保存每一个节点的上一个节点,这样在找到最短结果的时候,以此找到该节点的上一个节点,加入链表即可。
需要注意的几点:
(1)构建邻接链表前,需要把start和end加入,这样才能找到结果。
(2)构建邻接链表的时候,对于每一个单词,一定要在每一次替换char后,再将其恢复,以便进行下一次判断。
(3)每一层的queue用一个num来记录此层的Node个数,只要是找到一个,就证明这一层即为最短路径,设标志位为true,将该层循环之后,结束循环。
(4)循环的过程中,需要判断节点是否已经被判断过,创建一个Set用于记录所有判断过的节点。
代码:
public class Solution { private class Node{ public Node pre; public String val; public Node(Node p, String s){ pre = p; val = s; } } public List<List<String>> findLadders(String start, String end, Set<String> dict) { dict.add(start); dict.add(end); //注意(1) Map<String, Set<String>> neighbours = calNeighbours(dict); List<List<String>> req = new ArrayList<List<String>>(); LinkedList<Node> queue = new LinkedList<Node>(); queue.offer(new Node(null, start)); //开始节点的前一个为null Set<String> visited = new HashSet<String>(); //注意(4) boolean flag = false; //注意(3) while(!queue.isEmpty() || visited.size() == dict.size()){ int num = queue.size(); for(int i = 0 ; i < num ; i++){ Node n = queue.poll(); if(end.equals(n.val)){ findPath(n, req); flag = true; //注意(3) }else{ Set<String> temp = neighbours.get(n.val); if(temp == null || temp.size() == 0) continue; else{ for(String s : temp){ if(!visited.contains(s)){ //注意(4) queue.offer(new Node(n, s)); } } } } visited.add(n.val);//注意(4) } if(flag) break; } if(!flag) // 如果flag为false,证明无解 return new ArrayList<List<String>>(); return req; } private void findPath(Node n, List<List<String>> req) { // TODO Auto-generated method stub List<String> temp = new ArrayList<String>(); while(n != null){ temp.add(0, n.val); n = n.pre; } req.add(temp); } public Map<String, Set<String>> calNeighbours(Set<String> dict) { Map<String, Set<String>> neighbours = new HashMap<String, Set<String>>(); for(String str : dict){ int len = str.length(); char[] chars = str.toCharArray(); for(int i = 0 ; i < len ; i++){ char old = chars[i]; //注意(2) for(char c = 'a' ; c <= 'z' ; c++){ if(c == chars[i]) continue; chars[i] = c; String newstr = new String(chars); if(dict.contains(newstr)) { Set<String> set = neighbours.get(str); if(set != null) set.add(newstr); else{ Set<String> newset = new HashSet<String>(); newset.add(newstr); neighbours.put(str, newset); } } chars[i] = old ; //注意(2)引用型变量 每一次用完要还原 } // end fot c } // end for len } // end for str return neighbours; } }
AC时间为 1120ms左右,因此还有很大的改进空间,继续新的思路。
改进1:
在判断去重的时候,不仅要去掉已经走过的String,还要去掉同层的其他String,同层的路径一定回比下一层的要短,因此要在上一次循环的时候去掉所有新添加的,因此,每一层新建立一个remove的集合,存储该层需要去掉的节点。
修改后代码(只添加了三行 24, 39, 45):
public class Solution { private class Node{ public Node pre; public String val; public Node(Node p, String s){ pre = p; val = s; } } public List<List<String>> findLadders(String start, String end, Set<String> dict) { dict.add(start); dict.add(end); //注意(1) Map<String, Set<String>> neighbours = calNeighbours(dict); List<List<String>> req = new ArrayList<List<String>>(); LinkedList<Node> queue = new LinkedList<Node>(); queue.offer(new Node(null, start)); //开始节点的前一个为null Set<String> visited = new HashSet<String>(); //注意(4) boolean flag = false; //注意(3) while(!queue.isEmpty() || visited.size() == dict.size()){ int num = queue.size(); Set<String> remove = new HashSet<String>(); // 新加判断 每一层需要删除的集合 for(int i = 0 ; i < num ; i++){ Node n = queue.poll(); if(end.equals(n.val)){ findPath(n, req); flag = true; //注意(3) }else{ Set<String> temp = neighbours.get(n.val); if(temp == null || temp.size() == 0) continue; else{ for(String s : temp){ if(!visited.contains(s)){ //注意(4) queue.offer(new Node(n, s)); } remove.add(s); } } } //visited.add(n.val); // 可以去掉此处 } visited.addAll(remove); if(flag) break; } if(!flag) // 如果flag为false,证明无解 return new ArrayList<List<String>>(); return req; } private void findPath(Node n, List<List<String>> req) { // TODO Auto-generated method stub List<String> temp = new ArrayList<String>(); while(n != null){ temp.add(0, n.val); n = n.pre; } req.add(temp); } public Map<String, Set<String>> calNeighbours(Set<String> dict) { Map<String, Set<String>> neighbours = new HashMap<String, Set<String>>(); for(String str : dict){ int len = str.length(); char[] chars = str.toCharArray(); for(int i = 0 ; i < len ; i++){ char old = chars[i]; //注意(2) for(char c = 'a' ; c <= 'z' ; c++){ if(c == chars[i]) continue; chars[i] = c; String newstr = new String(chars); if(dict.contains(newstr)) { Set<String> set = neighbours.get(str); if(set != null) set.add(newstr); else{ Set<String> newset = new HashSet<String>(); newset.add(newstr); neighbours.put(str, newset); } } chars[i] = old ; //注意(2)引用型变量 每一次用完要还原 } // end fot c } // end for len } // end for str return neighbours; } }
改完之后 AC的时间大概是 820ms 应该还有继续修改的空间,持续更新.....
参考链接:
http://www.cnblogs.com/springfor/p/3893529.html
http://www.cnblogs.com/shawnhue/archive/2013/06/05/leetcode_126.html