Word Ladder II Java
来源:互联网 发布:大数据时代读后感5000 编辑:程序博客网 时间:2024/05/18 06:25
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”]
]
Note:
All words have the same length.
All words contain only lowercase alphabetic characters.
这道题,如果从实现功能的角度来说,感觉应该不难,但是,这道题对时间的要求太严格。。。。先后尝试了3次,都华丽丽地超时了,我只想说。。。。我的心好累。。。。
刚看到这道题,第一个天真的想法是,递归(其实就是深度遍历,但当时没想那么多),在找到结果时,记录一下当前结果长度,如果长度比结果集中的长度小,清空所有结果集。在寻找结果的时候,如果当前长度超过了最小结果长度,则停止寻找,程序如下:
private int length=Integer.MAX_VALUE; private List<List<String>> rst=new ArrayList<List<String>>(); public List<List<String>> findLadders3(String start, String end, Set<String> dict) { List<String> curRst=new ArrayList<String>(); curRst.add(start); helper(start, end, dict, curRst); return rst; } private void helper(String start, String end, Set<String> dict,List<String> preRst){ if(preRst.size()>=length) return; if(cantransform(start, end)){ List<String> curRst=new ArrayList<String>(preRst); curRst.add(end); int curLength=curRst.size(); if(curLength<length){ rst.clear(); length=curLength; } rst.add(curRst); return; } for(String s:dict){ if(!preRst.contains(s)&&cantransform(start, s)){ preRst.add(s); helper(s, end, dict, preRst); preRst.remove(preRst.size()-1); } } } private boolean cantransform(String a,String b){ if(a.equals(b)||a.length()!=b.length()) return false; int counter=0; for(int i=0;i<a.length();i++){ if(a.charAt(i)!=b.charAt(i)) counter++; } return counter==1; }
结果华丽丽地超时了,然后分析了一下代码,不难发现,以上代码采用了深度优先的算法,但是因为要求的是最短的结果,所以应该使用广度优先的算法,但是广度优先算法需要额外的存储空间,但是既然对时间的要求比较高,也只有牺牲空间了,于是有了第二版代码:
public List<List<String>> findLadders2(String start, String end, Set<String> dict){ List<List<String>> rst=new LinkedList<List<String>>(); boolean done=false; List<String> singleRst=new ArrayList<String>(); singleRst.add(start); rst.add(singleRst); Set<String> set=new HashSet<String>(); while(!done&&rst.size()!=0){ List<List<String>> curRst=new LinkedList<List<String>>(); for(List<String> list:rst){ String s=list.get(list.size()-1); if(cantransform(s, end)){ done=true; list.add(end); curRst.add(list); }else { for(String e:dict){ if(!set.contains(e)&&cantransform(s, e)){ List<String> curList=new ArrayList<String>(list); curList.add(e); curRst.add(curList); set.add(e); } } } } rst=curRst; } List<List<String>> removeList=new LinkedList<List<String>>(); for(List<String> list:rst){ if(!list.get(list.size()-1).equals(end)){ removeList.add(list); } } rst.removeAll(removeList); return rst; }
本以为这次的程序应该没问题了,但是结果还是给了我一个华丽的超时,我就不开心了。。。但是仔细分析了一下超时的测试用例,以及稍微看了看别人的博客,发现,超时的用例中,给的字典的长度特别长,而start和end的长度略微小一些,所以又有了第三个想法,在查找时,并不是直接遍历字典,看当前的string能否转换成字典中的特定string,而是,改变当前string中的一个char,看是否在字典中。然后就有了第三版代码(PS:因为心情比较暴躁,所有这版代码可能可读性不是很强,如果不想看可以不看,因为,这段代码还是没有通过。。。。。):
public List<List<String>> findLadders4(String start, String end, Set<String> dict){ List<List<String>> rst=new LinkedList<List<String>>(); boolean done=false; List<String> singleRst=new ArrayList<String>(); singleRst.add(start); rst.add(singleRst); Set<String> set=new HashSet<String>(); Set<String> dictSet=new HashSet<String>(dict); int wordLength=start.length(); while(!done&&rst.size()!=0){ List<List<String>> curRst=new LinkedList<List<String>>(); Set<String> curSet=new HashSet<String>(); for(List<String> list:rst){ for(int i=0;i<wordLength;i++){ for(char c='a';c<='z';c++){ StringBuilder s=new StringBuilder(list.get(list.size()-1)); if(s.charAt(i)!=c){ s.setCharAt(i, c); String curString=s.toString(); if(curString.equals(end)){ done=true; list.add(end); curRst.add(list); break; }else if(dictSet.contains(curString)&&!set.contains(curString)){ List<String> curList=new ArrayList<String>(list); curList.add(curString); curRst.add(curList); curSet.add(curString); } } } } } set.addAll(curSet); dictSet.removeAll(set); rst=curRst; } List<List<String>> removeList=new LinkedList<List<String>>(); for(List<String> list:rst){ if(!list.get(list.size()-1).equals(end)){ removeList.add(list); } } rst.removeAll(removeList); return rst; }
在第三版的程序超时以后,真是累觉不爱了。。。。不知如何是好。。。于是我去了讨论区(最近特别喜欢讨论去,感觉讨论区某些代码比国内一些博客好多了,强烈推荐)。然后找到了一份比较喜欢的代码:
地址为:https://leetcode.com/discuss/9523/share-two-similar-java-solution-that-accpted-by-oj
List<List<String>> results; List<String> list; Map<String,List<String>> map; public List<List<String>> findLadders(String start, String end, Set<String> dict) { results= new ArrayList<List<String>>(); if(dict.size()==0) return results; int cuur=1,next=0; boolean found =false; list=new LinkedList<String>(); map=new HashMap<String, List<String>>(); Queue<String> queue = new ArrayDeque<String>(); Set<String> unvisited=new HashSet<String>(dict); Set<String> visited=new HashSet<String>(); queue.add(start); unvisited.add(end); unvisited.remove(start); while(!queue.isEmpty()){ String word=queue.poll(); cuur--; for(int i=0;i<word.length();i++){ StringBuilder builder=new StringBuilder(word); for(char c='a';c<='z';c++){ builder.setCharAt(i, c); String newWord=builder.toString(); if(unvisited.contains(newWord)){ if(visited.add(newWord)){ next++; queue.add(newWord); } if(map.containsKey(newWord)) map.get(newWord).add(word); else { List<String> list=new LinkedList<String>(); list.add(word); map.put(newWord, list); } if(newWord.equals(end)&&!found) found=true; } } } if(cuur==0){ if(found) break; cuur=next; next=0; unvisited.removeAll(visited); visited.clear(); } } backTrace(end, start); return results; } private void backTrace(String word,String start){ if(word.equals(start)){ list.add(0,start); results.add(new ArrayList<String>(list)); list.remove(0); return; } list.add(0,word); if(map.get(word)!=null) for(String s:map.get(word)) backTrace(s, start); list.remove(0); }
这段代码即可AP。
下面分析一下,这段代码和我的第三版程序的异同,以及这段程序的可取之处:
首先,大体的思路是一样的,都是广度优先遍历。
其次,不同之处在于,对细节的优化,在我的算法中,使用了两个set:记录访问过的set和记录没访问过的set,以及一个临时set。而他的算法中,也有两个set,visitedSet和unvisitedSet。差别之处在于,他的算法中没有临时set,而是将每次循环遍历的值都存在visitedSet中,并且在循环结束后,执行unvisited.removeAll(visited);和visited.clear();动作,明显提升了效率。
所以按照他的优化方法,我对我的程序进行了小优化以后,发现竟然AP了,细节决定成败啊。。。。:
以下是优化后的代码:
但是看了一下leetcode的结果,我优化后的程序,是1854ms,而他的程序是730ms。还是有很大差别的。当前的想法是,可能是他使用了map的原因,map的存取比较快,而我使用“蛮力”实现,还有很多存在优化的地方,这个问题留给以后再解决吧,或者有谁看到了,知道产生差距的原因,可以一起讨论一下。
好的,就这样吧,我只想说,我的心。。好累。。。。
public List<List<String>> findLadders4(String start, String end, Set<String> dict){ List<List<String>> rst=new LinkedList<List<String>>(); boolean done=false; List<String> singleRst=new ArrayList<String>(); singleRst.add(start); rst.add(singleRst); Set<String> set=new HashSet<String>(); Set<String> dictSet=new HashSet<String>(dict); int wordLength=start.length(); while(!done&&rst.size()!=0){ List<List<String>> curRst=new LinkedList<List<String>>(); for(List<String> list:rst){ for(int i=0;i<wordLength;i++){ for(char c='a';c<='z';c++){ StringBuilder s=new StringBuilder(list.get(list.size()-1)); if(s.charAt(i)!=c){ s.setCharAt(i, c); String curString=s.toString(); if(curString.equals(end)){ done=true; list.add(end); curRst.add(list); break; }else if(dictSet.contains(curString)){ List<String> curList=new ArrayList<String>(list); curList.add(curString); curRst.add(curList); set.add(curString); } } } } } dictSet.removeAll(set); set.clear(); rst=curRst; } List<List<String>> removeList=new LinkedList<List<String>>(); for(List<String> list:rst){ if(!list.get(list.size()-1).equals(end)){ removeList.add(list); } } rst.removeAll(removeList); return rst; }
- Word Ladder II Java
- [Leetcode] Word Ladder II (Java)
- [LeetCode][Java] Word Ladder II
- Word Ladder/ Word Ladder II
- [leetcode-126]Word Ladder II(java)
- [LeetCode] 126. Word Ladder II java
- Word Ladder II
- Word Ladder II
- 【leetcode】Word Ladder II
- [LeetCode]Word Ladder II
- Word Ladder II
- [leetcode] Word Ladder II
- LeetCode - Word Ladder II
- Word Ladder II
- Leetcode: Word Ladder II
- leetcode Word Ladder II
- Leetcode Word Ladder II
- LeetCode | Word Ladder II
- 如何更好地给同事讲代码?
- zoj 1745 Are We There Yet?
- Ubuntu安装Oracle InstantClient
- Tracking Learning Detecting-TLD 学习笔记1
- SSH三大框架搭建的步骤
- Word Ladder II Java
- Hibernate映射的关系问题
- Android Hal层简要分析
- 读书无用论,学历无用论,以及其他
- SSH三大框架整合原理
- 自定义可以存放任意类型(含智能指针)的顺序栈
- 腾讯公司后台服务器经典面试题 (2009年5月)
- 学习实战全笔记--JavaSE--包装类的特性--用法示例(JDK8)
- many to one