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;    }
0 0
原创粉丝点击