126. Word Ladder II

9 阅读1分钟

image.png

bfs + dfs

  • Use BFS to: Build a graph of valid paths using a parent map

    • Note1: node could have many parent, so we use stepMap, and explore by level. e.g.
    • Note2: going back to visited node will not be shortest path. e.g.
  • Use DFS (backtracking) to: Reconstruct all paths from endWord back to beginWord using the parent map

  • BFS Time = O(N × L × 26) = O(N × L)

  • DFS Time = O(M × K)

  • M = total number of shortest transformation sequences K = average length of each sequence (at most N)

image.png

class Solution {
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        Set<String> set = new HashSet<>(wordList);

        // BFS queue
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);

        // <word, lists of parent> stores words it transformed from
        Map<String, List<String>> parent = new HashMap<>();

        // for each visited word, record BFS level
        Map<String, Integer> stepMap = new HashMap<>();
        stepMap.put(beginWord, 0);
        int step = 1;

        boolean found = false;

        while (!queue.isEmpty()) {
            int size = queue.size();
            // for each step(bfs layer)
            for (int s = 0; s < size; s++) {
                String word = queue.poll();                

                for (int i = 0; i < word.length(); i++) {
                    char[] arr = word.toCharArray();
                    for (char j = 'a'; j <= 'z'; j++) {
                        arr[i] = j;
                        String newWord = String.valueOf(arr);

                        // ? already seen newWord at this level (step)
                        // ? then the current word is another shortest-path parent of newWord
                        if (stepMap.containsKey(newWord) && step == stepMap.get(newWord)) {
                            parent.get(newWord).add(word);
                        }

                        // generated substitute exists in wordList
                        if (set.contains(newWord) ) {
                            queue.offer(newWord);
                            
                            // ? Prevent further visits (we only want shortest paths)
                            set.remove(newWord); 

                            parent.putIfAbsent(newWord, new ArrayList<>());
                            parent.get(newWord).add(word);

                            // explored at this step
                            stepMap.put(newWord, step);

                            if (newWord.equals(endWord)) {
                                found = true;// break after this step
                            }

                        }

                    }
                }
            }

            step++;

            if (found) {
                break;
            }

        }

        // use dfs backtrack to collect all path
        List<List<String>> res = new ArrayList<>();

        if (found) {
            Deque<String> path = new LinkedList<>();
            path.add(endWord);
            dfs(parent, endWord, beginWord, path, res);

        }
        return res;
    }


    // DFS from end to start, find all the paths
    public void dfs(Map<String, List<String>> parent, String cur, String beginWord, Deque<String> path, List<List<String>> res) {
        if (cur.equals(beginWord)) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (String prev : parent.get(cur)) {
            path.addFirst(prev);
            dfs(parent, prev, beginWord, path, res);
            path.removeFirst();
        }
    }
}