Word Ladder II Java

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值