Question

Formatted question description: https://leetcode.ca/all/126.html

126. Word Ladder II

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

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


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"]
]

Example 2:

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

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.


@tag-backtracking

Algorithm

Because we want to record the path, we need to record the parent node for each node, so that at the end, we will find its parent node one by one from the end point, until we find the starting point, and then flip it and add it to the result set.

Probably the process is similar, but the difference is that when deleting a string in the dictionary, this character may be used in another path. It is like this:

A -> C -> D, B -> C -> D

They will all go through C, and both are the shortest paths. When A is searched for C, and C is deleted from the dictionary. When B is searching for a string with a distance of 1, C is no longer in the dictionary.

So what to do?

We set up a hash table to store a set of parent nodes of a string, so that C is not in the dictionary and then check the hash table to see if C is in the hash table, if it is, and the parent node level of C Same as B, then B is also added to the parent node combination of C. It can be known that the distance of the parent node set of a string from the starting point must be equal, that is, they are the shortest distance.

Finally, after traversing all the points, use DFS to find all sets from the end point forward.

Code

Java

  • import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Queue;
    import java.util.Set;
    
    public class Word_Ladder_II {
    
    	// iteration bfs
    	// https://leetcode.com/problems/word-ladder-ii/solution/
        public class Solution {
    
            List<List<String>> list = new ArrayList<List<String>>();
    
            public List<List<String>> findLadders(String start, String end, Set<String> dict) {
    
                if (start == null || end == null || dict == null) return list;
    
                dict.add(end); // !!!
    
                Queue<String> q = new LinkedList<String>();
                int level = 1;
                int currentLevelCount = 1;
                int newLevelCount = 0;
                boolean found = false;
                int foundLevel = -1;
    
                // from end word, to all paths
                HashMap<String, ArrayList<ArrayList<String>>> hm = new HashMap<String, ArrayList<ArrayList<String>>>();
    
    
                q.offer(start);
                ArrayList<String> singlePath = new ArrayList<String>();
                ArrayList<ArrayList<String>> allPaths = new ArrayList<ArrayList<String>>();
    
                singlePath.add(start);
                allPaths.add(singlePath);
                hm.put(start, allPaths);
    
                while (!q.isEmpty()) {
    
                    String current = q.poll();
                    currentLevelCount--; // 这里用了新旧count来标记每个level,没有用null
    
                    for (int i = 0; i < current.length(); i++) {
                        char[] array = current.toCharArray();
    
                        for (char c = 'a'; c <= 'z'; c++) {
                            array[i] = c;
                            String each = new String(array);
                            if (each.equals(end)) {
                                found = true;
                                foundLevel = level;
                            }
    
    
                            if (dict.contains(each)) {
                                // q.offer(each);
                                newLevelCount++;
    
                                ArrayList<ArrayList<String>> prevAllPaths = hm.get(current);
    
                                if (hm.containsKey(each))
                                    allPaths = hm.get(each);
                                else {
                                    /* @note@note:
    	                                enqueue is here. if no path ending at this one, then has to explore in future
    	                                if there is path ending at this one, meaning it's been explored already. no need to enqueue
    	                            */
                                    q.offer(each);
                                    allPaths = new ArrayList<ArrayList<String>>();
                                    hm.put(each, allPaths);
                                }
    
                                // @note@note: this if is the key !!! no path for new word, or new word path is one more than previous path
                                // using this if, the"if visited" check can be removed
                                // if (allPaths.size() == 0 || prevAllPaths.size() + 1 == allPaths.size()) {
                                if (allPaths.size() == 0 || prevAllPaths.get(0).size() + 1 == allPaths.get(0).size()) {
                                    for (ArrayList<String> eachPath : prevAllPaths) {
                                        ArrayList<String> newone = new ArrayList<String>(eachPath);
                                        newone.add(each);
                                        allPaths.add(newone);
                                    }
                                }
                            }
                        }
                    }
    
                    // @note@note: also the key, to make sure only find shortest
                    if (found && foundLevel != level) {
                        break;
                    }
    
                    // @note: must be after trying the last word of currentLevel, then update
                    if (currentLevelCount == 0) {
                        currentLevelCount = newLevelCount;
                        newLevelCount = 0;
                        level++;
    
                    }
    
                }
    
                if (!found) {
                    return list;
                }
    
                for (ArrayList<String> each : hm.get(end)) {
                    list.add(each);
                }
    
                return list;
    
            }
        }
    
    
        public class Solution_recursion {
    
            private List<String> findPath(String fromWord, String toWord,
                                          Set<String> seenWords) {
    
                if (fromWord.equals(toWord)) {
                    ArrayList<String> result = new ArrayList<>();
                    result.add(toWord);
                    return result;
                }
    
                // Find all words that you can go to from fromWord
                List<String> nextWords = getNextWords(fromWord, seenWords);
                for (String word : nextWords) {
                    Set<String> newSeenWords = new HashSet<String>(seenWords);
                    newSeenWords.add(word);
                    List<String> subPath = findPath(word, toWord, newSeenWords);
                    if (subPath != null) {
                        subPath.add(fromWord);
                        return subPath;
                    }
                }
    
                // There wasn't a path
                return null;
    
            }
    
            private final List<String> WORDS = Arrays.asList("head", "heal",
                "teal", "tell", "tall", "tail");
    
            private final List<Character> ALPHA = Arrays.asList('a', 'b',
                'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
    
            private final HashSet<String> DICTIONARY = new HashSet<String>(
                WORDS);
    
            private List<String> getNextWords(String fromWord,
                                              Set<String> seenWords) {
                List<String> outList = new ArrayList<String>();
                StringBuilder builder;
                for (int i = 0; i < fromWord.length(); i++) {
                    builder = new StringBuilder(fromWord);
                    for (Character j : ALPHA) {
                        if (j == fromWord.charAt(i)) {
                            continue;
                        }
                        builder.setCharAt(i, j);
                        String potentialWord = builder.toString();
                        if (DICTIONARY.contains(potentialWord)
                            && !seenWords.contains(potentialWord)) {
                            outList.add(potentialWord);
                        }
                    }
                }
                return outList;
            }
        }
    }
    
  • Todo
    
  • from collections import deque
    
    
    class Solution(object):
      def findLadders(self, beginWord, endWord, wordlist):
        """
        :type beginWord: str
        :type endWord: str
        :type wordlist: Set[str]
        :rtype: List[List[int]]
        """
    
        def getNbrs(src, dest, wordList):
          res = []
          for c in string.ascii_lowercase:
            for i in range(0, len(src)):
              newWord = src[:i] + c + src[i + 1:]
              if newWord == src:
                continue
              if newWord in wordList or newWord == dest:
                yield newWord
    
        def bfs(beginWord, endWord, wordList):
          distance = {beginWord: 0}
          queue = deque([beginWord])
          length = 0
          while queue:
            length += 1
            for k in range(0, len(queue)):
              top = queue.popleft()
              for nbr in getNbrs(top, endWord, wordList):
                if nbr not in distance:
                  distance[nbr] = distance[top] + 1
                  queue.append(nbr)
          return distance
    
        def dfs(beginWord, endWord, wordList, path, res, distance):
          if beginWord == endWord:
            res.append(path + [])
            return
    
          for nbr in getNbrs(beginWord, endWord, wordList):
            if distance.get(nbr, -2) + 1 == distance[beginWord]:
              path.append(nbr)
              dfs(nbr, endWord, wordList, path, res, distance)
              path.pop()
    
        res = []
        distance = bfs(endWord, beginWord, wordlist)
        dfs(beginWord, endWord, wordlist, [beginWord], res, distance)
        return res
    
    

All Problems

All Solutions