126. Word Ladder II

A transformation sequence from word beginWord to word endWord using a dictionary wordList is a sequence of words beginWord -> s1 -> s2 -> ... -> sk such that:

  • Every adjacent pair of words differs by a single letter.

  • Every si for 1 <= i <= k is in wordList. Note that beginWord does not need to be in wordList.

  • sk == endWord

Given two words, beginWord and endWord, and a dictionary wordList, return all the shortest transformation sequences from beginWord to endWord, or an empty list if no such sequence exists. Each sequence should be returned as a list of the words [beginWord, s1, s2, ..., sk].

Example 1:

Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
Output: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
Explanation: There are 2 shortest transformation sequences:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"

Example 2:

Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
Output: []
Explanation: The endWord "cog" is not in wordList, therefore there is no valid transformation sequence.

Constraints:

  • 1 <= beginWord.length <= 5

  • endWord.length == beginWord.length

  • 1 <= wordList.length <= 1000

  • wordList[i].length == beginWord.length

  • beginWord, endWord, and wordList[i] consist of lowercase English letters.

  • beginWord != endWord

  • All the words in wordList are unique.

Solution:

https://www.jiuzhang.com/problem/word-ladder-ii/ Version 1:

从 end 到 start 做一次 BFS,并且把距离 end 的距离都保存在 distance 中。 然后在从 start 到 end 做一次 DFS,每走一步必须确保离 end 的 distance 越来越近。

这种解法是:end -> start : bfs 。start -> end : dfs

与另外一个代码中提前建立 index 不同,这里是在寻找下一个变换单词的时候,再去获得对应的单词列表。一个单词最多有 L 个字符,每个字符有 25 种不同的变化(26个字母除掉这个位置上的字母),然后 check 一下在不在 dict 里就知道是不是 next word 了。

/class Solution {
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
    
        List<List<String>> result = new ArrayList<>();
        Set<String> dict = new HashSet<String>();
        // need to add this stage, otherwise it always return null
        dict.add(beginWord);
        for(String word : wordList)
        {
            dict.add(word);
        }
        if(!dict.contains(endWord)) return result;
        
        Map<String, Integer> distance = new HashMap<String, Integer>();
        bfs(distance, endWord, beginWord, dict);
        
        List<String> path = new ArrayList<String>();
        // first add the beginWord
        path.add(beginWord);
        dfs(result, path, distance, beginWord, endWord, dict);
        return result;
    }
    
    
    public void bfs(Map<String, Integer> distance , String beginWord, String endWord, Set<String> wordList)
    {
        Queue<String> queue = new LinkedList<String>();
        queue.offer(beginWord);
        distance.put(beginWord, 0);
        while(!queue.isEmpty())
        {
            for(int i = 0; i< queue.size(); i++)
            {
                String current = queue.poll();
                if(current.equals(endWord))
                {
                    return;
                }
                List<String> neighbors  = getNeighbours(current, wordList);
                for(String next: neighbors)
                {
                    if(!distance.containsKey(next))
                    {
                        queue.offer(next);
                        distance.put(next, distance.get(current) + 1);
                    }
                }
            }
        }
    }
    
    public void dfs(List<List<String>> result, 
                    List<String> path,
                    Map<String, Integer> distance, 
                    String currentWord, 
                    String endWord, 
                    Set<String> wordList)
    {
        //path.add(currentWord);
        if(currentWord.equals(endWord))
        {
            result.add(new ArrayList<String>(path));
            // can't add return;
        }
        else
        {
            for(String neighbor: getNeighbours(currentWord, wordList))
            {
                if(distance.containsKey(neighbor) && distance.containsKey(currentWord) && distance.get(currentWord) == distance.get(neighbor) + 1)
                {
                    path.add(neighbor);
                    dfs(result, path, distance, neighbor, endWord, wordList);
                    path.remove(path.size()-1);
                }
            }
        } 
        //path.remove(path.size()-1);
    }

    
    public List<String> getNeighbours(String current, Set<String> dict)
    {
        List<String> result = new ArrayList<String>();
        for(int i = 0; i< current.length(); i++)
        {
            char c = current.charAt(i);
            for(char j = 'a' ; j <= 'z'; j++)
            {
                if(c == j) continue;
                char[] wordArray = current.toCharArray();
                wordArray[i] = j;
                String neighbor = new String(wordArray);
                if(dict.contains(neighbor))
                {
                    result.add(neighbor);
                }
            }
        }
        return result;
    }
}

Version 2:

BFS from start to end寻找最短路径,DFS from end to start 找到所有路径。

思路上和Word Ladder是比较类似的,但是因为是要求出所有路径,仅仅保存路径长度是不够的,而且这里还有更多的问题,那就是为了得到所有路径,不是每个结点访问一次就可以标记为visited了,因为有些访问过的结点也会是别的路径上的结点,所以访问的集合要进行回溯(也就是标记回未访问)。所以时间上不再是一次广度优先搜索的复杂度了,取决于结果路径的数量。同样空间上也是相当高的复杂度,因为我们要保存过程中满足的中间路径到某个数据结构中,以便最后可以获取路径,这里我们维护一个HashMap,把一个结点前驱结点都进行保存。在LeetCode中用Java实现上述算法非常容易超时。为了提高算法效率,需要注意一下两点:

1)在替换String的某一位的字符时,先转换成char数组再操作;

2)如果按照正常的方法从start找end,然后根据这个来构造路径,代价会比较高,因为保存前驱结点容易,而保存后驱结点则比较困难。

所以我们在广度优先搜索时反过来先从end找start,最后再根据生成的前驱结点映射从start往end构造路径,这样算法效率会有明显提高。

 public List<List<String>> findLadders(String start, String end, Set<String> dict) {

       //store the node and its neighbor
       HashMap<String,List<String>> graph=new HashMap<String,List<String>>();
       //store the distance with end
       HashMap<String,Integer> distance=new HashMap<String,Integer>();
       dict.add(start);
       dict.add(end);

       bfs(start,end,dict,graph,distance);

       List<List<String>> result=new ArrayList<>();
       List<String> path=new ArrayList<String>();

       dfs(result,path,start,end,graph,distance);

       return result;
   }

   public List<String> neighbor(String str,Set<String> dict){
       List<String> neighbor=new ArrayList<String>();
       for(int i=0;i<str.length();i++){
           char ch=str.charAt(i);
           for(char j='a';j<='z';j++){
               if(ch==j) continue;
               //这三句话生成新的string
               char[] string=str.toCharArray();
               string[i]=j;
               String newstr=new String(string);

               if(dict.contains(newstr)){
                   neighbor.add(newstr);
               }
           }
       }
       return neighbor;
   }

   //from start to end
   public void bfs(String start,
                   String end,
                   Set<String> dict,
                   HashMap<String,List<String>> graph,
                   HashMap<String,Integer> distance){
       Queue<String> queue=new LinkedList<String>();
       queue.add(start);
       distance.put(start,0);
       //initial graph
       for(String str: dict){
           graph.put(str, new ArrayList<String>());
       }

       while(!queue.isEmpty()){
           String current=queue.poll();
           for(String element :neighbor(current,dict)){
               graph.get(current).add(element);
               if(!distance.containsKey(element)){
                   distance.put(element,distance.get(current)+1);
                   queue.offer(element);
               }
           }
       }

   }

   //from end to start
   public void dfs(List<List<String>> result,
                   List<String> path,
                   String start,
                   String current,
                   HashMap<String,List<String>> graph,
                   HashMap<String,Integer> distance){
       path.add(current);
       if(current.equals(start)){
           Collections.reverse(path);
           result.add(new ArrayList<String>(path));
           Collections.reverse(path);
       }else{
           for(String next :graph.get(current)){
               if(distance.containsKey(next) && distance.get(current)==distance.get(next)+1){
                   dfs(result,path,start,next,graph,distance);
               }
           }
       }
       path.remove(path.size()-1);
   }

Last updated