Leetcode 127. Word Ladder I & 126. Word Ladder II

来源:互联网 发布:文字录入 网络兼职 编辑:程序博客网 时间:2024/05/17 09:25

连做带debug花了5个多小时,刷新了LRU Cache的记录。。


127. Word Ladder

Total Accepted: 71902 Total Submissions: 366336 Difficulty: Medium

Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation sequence from 

beginWord to endWord, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the word list

For example,

Given:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.

Note:

  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.


参考的blog地址:

https://cheonhyangzhang.wordpress.com/2015/10/01/127-leetcode-java-word-ladder-medium/ 

http://www.cnblogs.com/tonyluis/p/4532839.html

http://siukwan.sinaapp.com/?p=995

第一个地址双linkedlist解题,第二个是hashmap+linkedlist,两者当然是第一个快,第三个主要刚开始用来看解析的。


一开始看到这题,这不是当年某本书图论中的相似问题吗,只不过伪装了一下,现在变成了String,可以新建一个表,表示点(一个String)点之间的连通情况,

因为是无向图,于是邻接矩阵一定是对称的,于是问题就变成了:给一个邻接矩阵,求两点之间的最短路径。于是第一步先要产生表格,O(n^2)时间和空间;然后还要BFS。。想到这里,楼主就开始search了。。觉得这么琐碎(菜鸟都这样,因为不熟练觉得太琐碎,大牛看到啪啪啪30分钟打完了代码)。


于是搜索到了第三个blog一看,果然OJ会超时。。(吐槽一下mathwork那string reverse题目同样代码弄到OJ上改了半个多小时。。果然是专门盯着非法输入编写的test case啊!!)于是看了人家思路。现在word ladder II 博主也终于全部自己写了一遍,虽然百分比极低,但是作为全部手打,ecplise debug两小时的劳动成果,还是非常高兴!!扯远了。。


既然无法建立表,那么就换一种逻辑。。也真心巧妙,步骤如下:(shortest path当然是bfs,保证碰到的时候一定是最短的)


1. 取一个词,然后对其每一位,换成a~z,check是否在wordlist中,如果在说明可以到达,将其入队(java 中用linkedlist模拟)。trick是:入队之后,便从wordlist中将该match删去,这样就避免了重复case。(赞!)


2. 对其每一位都check之后,目前queue中的单词便是所有变化一次的单词了!下次自然是取一个元素再次进行1。当然1中没说的是如果变化后的单词有match给的target单词,那么就该返回长度,或者level数了。那么问题来了,怎么记录level数。


3.先看到的其实是hashmap的方法,队中是下个level的点,而map就负责存储入队时的level数,这样match的时候取出存储的Integer值就行了,至于+1还是直接返回,要看具体程序怎么写的,改进是扔掉hashmap,采用linkedlist做的另外一个queue,这样俩queue就好比一个hashmap,但是比hashmap灵活,

因为我们既要取出头一个点,又要取出对应的值,前者的操作占了绝大多数,所以还是list更符合要求。这样的话,一个点入queue时,其对应的level数也入另外一个队,这样同存同取即可。


这里有个大bug,博主碰到debug很久。一开始想偷懒,说你们为啥一个个都用俩结构,一个queue不行吗,于是写了个程序,每当queue出货的时候,更新level++,这样很明显错了比如hot可以到dot和lot,dot和lot同属于一级,那它俩应该对应一个level值,ok cool,改进成queue空的时候level++可以吗?NO。比如我取出了dot,发现dot可以变成dog,于是按照上面的逻辑,dog又入队了,这样level一直不增加,dog本来应该是增加的,就少了。


简单来说,就是一个queue是无法建立对应的关系的,要么用HashMap,直接保存了level信息,要么用双list,也保存了对应信息。重点就是对应!因为无法知道什么时候改变,所以只能存了,取出来一看是多少就是多少+1。举得例子都是卡住的case,若你也卡住了很久,不妨看看这两段话。


解法一:HashMap+LinkedList (as Queue)

public class Solution { //109ms    public int ladderLength(String beginWord, String endWord, Set<String> wordList) {        LinkedList<String> queue = new LinkedList<String>();        HashMap<String, Integer> hm = new HashMap<String, Integer>();        queue.add(beginWord); wordList.remove(beginWord);        int length=1;        hm.put(beginWord,length);                while(queue.size()>0){            String first = queue.removeFirst();            int curlength = hm.get(first)+1;            char[] cur = first.toCharArray();                        for(int i=0; i<cur.length; i++){                char tempchar = cur[i];                for (int j=0; j<26; j++){                    if (tempchar=='a'+j) continue;                    cur[i]=(char)('a'+j);                                        String temp = new String(cur); //////////////////////////////                                        if (temp.equals(endWord)) {                        return curlength;                    }                                        if (wordList.contains(temp)){                        wordList.remove(temp);                        queue.add(temp);                        hm.put(temp,curlength);                    }                }                cur[i] = tempchar;            }                    }        return 0;}}
解法二:双LinkedList

public class Solution {    public int ladderLength(String beginWord, String endWord, Set<String> wordList) {        LinkedList<String> queue = new LinkedList<String>();        LinkedList<Integer> len = new LinkedList<Integer>();        queue.add(beginWord); wordList.remove(beginWord);        len.add(1);                while(queue.size()>0){            String first = queue.removeFirst();            int curlength = len.removeFirst();                        char[] cur = first.toCharArray();                        for(int i=0; i<cur.length; i++){                char tempchar = cur[i];                for (int j=0; j<26; j++){                    if (tempchar=='a'+j) continue;                    cur[i]=(char)('a'+j);                                        String temp = new String(cur); //////////////////////////////                                        if (temp.equals(endWord)) {                        return curlength+1;                    }                                        if (wordList.contains(temp)){                        wordList.remove(temp);                        queue.add(temp);                        len.add(curlength+1);                    }                }                cur[i] = tempchar;            }                    }        return 0;}}
同解法大同小异的另外一版,这题非常繁琐,debug疯掉,所以code不美观,客官见谅。

public class Solution {    public int ladderLength(String beginWord, String endWord, Set<String> wordDict) {        LinkedList<String> queue = new LinkedList<String>();        LinkedList<Integer> level = new LinkedList<Integer>();                if (beginWord == null || endWord == null) return 0;                queue.add(beginWord); level.add(1);        wordDict.remove(beginWord);              while(queue.size()>0){            String temp = queue.removeFirst();            int lvl = level.removeFirst();            char[] cur = temp.toCharArray();                                   for(int i=0; i<cur.length; i++){                char tempchar = cur[i];                for (int j=0; j<26; j++){                    if (tempchar=='a'+j) continue;                    cur[i]=(char)('a'+j);                                    String curString = new String(cur);                    if(curString.equals(endWord)){                        return lvl+1;                    }                                    if(wordDict.contains(curString)){                        wordDict.remove(curString);                        queue.add(curString);                        level.add(lvl+1);                    }                }                cur[i] = tempchar;            }        }        return 0;    }}


126. Word Ladder II

Total Accepted: 41927 Total Submissions: 308711 Difficulty: Hard

Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord to 

endWord, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the word list

For example,

Given:
beginWord = "hit"
endWord = "cog"
wordList = ["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.


刚一看到,卧槽,长度不满足了,还要路径?


这题繁琐的地方在于,无法正常backtracking,以为前面一题的解法都是iterative的,backtracking的要点就是递归。当然网上是有递归版本,但他们都有建立自己的数据结构,从而满足递归的要求。大神轻拍,做了5个小时就自己目前理解是这样。BFS先算一遍,知道长度,然后DFS递归。。还要套backtracking的方法。。怪不得做完才知道AC率很低,trivial的题。当然还是强行backtracking了,看下面。


自己思路如下:

一开始上个题目算出了最短路径长度,返回了int;那么现在我完全可以让它返回一个List<List<String>>,也就是上一个题中的每一层的信息levels,比如我们start的单词在该List的0 index出,变化一次的单词存在 1 index处,依次类推。这样假如上个题的返回值是5,那么现在这个levels中应该有4个元素,包括start,和end前一步的单词。


有了这个levels就好办了。就变成了,有目标单词,有起始单词,有intermediate单词。很爽吧,注意有两个隐形陷阱:

1. 要求的是最短路径长度,不是说变到目标就行了,所以上个题只要碰到就该返回levels了。(这里还有个终极陷阱下面会说)。

2. levels中是记录了可以到达的点没错,但是!并不是互相都通的。这层两个,下层2个,哪个可以到哪个是还要再次check的。针对这点,网上有很多自创class的解法,相当于类似双边linkedlist的节奏,就省去了再次检查的麻烦。博主还是check了一遍。


于是思路变成这样:

有了levels之后,每次还要check下一层的一个元素是不是和目前temp的最后一个元素match,match才能进一步dfs,否则换一个check。因为match还是string差一位的match检查,然后每一个都要对每一层的元素check,所以相当耗时。。


不过为的就是写出自己理解的代码,这个题如此trivial,能按照自己的理解写出来,跑通就可以了。

上自己写的代码,耗时680MS,函数是static因为在IDE中测试debug了很久,一个个function打印变量找错。。

先说上面提到的终极陷阱,碰到target返回levels没错,但是!!!!!!!比如a,c, wordlist是a,b,c。level 0 是a没问题,然后变化a, b符合放到了level1,没问题,变化到c 发现match target,请问直接返回levels吗?NO。要删除b这一层的元素,来满足最短。

public class Solution {    public static List<List<String>> findLadders(String start, String end, Set<String> wordList) {        List<List<String>> res = new ArrayList<List<String>>();        List<List<String>> levels = ladderLength(start, end, wordList);        if (levels.size()==0) return res;        List<String> temp = new ArrayList<String>(); temp.add(start);                      placeInto(end, 1, temp, levels, res);        return res;    }        static boolean canMatch(String a, String b){        if (a.length()!=b.length()) return false;        int count=0;        for (int i=0; i<a.length(); i++){            if(a.charAt(i)==b.charAt(i)) count++;        }        return count==a.length()-1;    }        static List<List<String>> ladderLength(String beginWord, String endWord, Set<String> wordDict) {        List<List<String>> cache = new ArrayList<List<String>>();        LinkedList<String> queue = new LinkedList<String>();        LinkedList<Integer> level = new LinkedList<Integer>();                if (beginWord == null || endWord == null) return cache;                queue.add(beginWord); level.add(0);        wordDict.remove(beginWord);                List<String> firstLevel = new ArrayList<String>();        firstLevel.add(beginWord);        cache.add(firstLevel);                while(queue.size()>0){            String temp = queue.removeFirst();            int lvl = level.removeFirst();            char[] cur = temp.toCharArray();                                   for(int i=0; i<cur.length; i++){                char tempchar = cur[i];                for (int j=0; j<26; j++){                    if (tempchar=='a'+j) continue;                    cur[i]=(char)('a'+j);                                    String curString = new String(cur);                    if(curString.equals(endWord)){                        if(cache.size()==lvl+2) cache.remove(cache.size()-1); ////////////////////////                    return cache;                    }                                    if(wordDict.contains(curString)){                        wordDict.remove(curString);                        queue.add(curString);                        level.add(lvl+1);                                                if (lvl+1==cache.size()) cache.add(new ArrayList<String>());                    cache.get(lvl+1).add(curString);                    }                }                cur[i] = tempchar;            }                   }        return new ArrayList<List<String>>();    }        static void placeInto(String end, int curlevel, List<String> temp, List<List<String>> levels, List<List<String>> res){        if (curlevel==levels.size()){        if(canMatch(temp.get(temp.size()-1), end)){            temp.add(end);            res.add(new ArrayList<String>(temp));            temp.remove(temp.size()-1);            return;        }        }else{                List<String> words = levels.get(curlevel);        for(int i=0; i<words.size(); i++){            String word = words.get(i);            if (canMatch(temp.get(temp.size()-1), word)){                temp.add(word);                placeInto(end, curlevel+1, temp, levels, res);                temp.remove(temp.size()-1);            }        }        }    }}

其实想想,通过这个题更想锻炼的是解题的耐心和debug的能力吧,具体的知识点bfs, dfs, backtracking相信大家都会,这题就是强行揉到一起。想清楚怎么做就够了。
0 0
原创粉丝点击