126. Word Ladder II

Hard

Problem Description

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

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

Examples

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.
Jump to Solution: Python Java C++ JavaScript C#

Python Solution


from collections import defaultdict, deque

def findLadders(beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
    # Create word set for quick lookup
    word_set = set(wordList)
    if endWord not in word_set:
        return []
    
    # Remove beginWord from word_set if it exists
    if beginWord in word_set:
        word_set.remove(beginWord)
    
    # Create a dictionary to store neighbors
    neighbors = defaultdict(list)
    
    # Build the neighbors dictionary
    def build_neighbors():
        for word in [beginWord] + list(word_set):
            for i in range(len(word)):
                pattern = word[:i] + '*' + word[i+1:]
                neighbors[pattern].append(word)
    
    build_neighbors()
    
    # BFS to find shortest paths
    level = {beginWord: [[beginWord]]}
    while level and endWord not in level:
        next_level = defaultdict(list)
        for word in level:
            for i in range(len(word)):
                pattern = word[:i] + '*' + word[i+1:]
                for nei in neighbors[pattern]:
                    if nei in word_set:
                        for path in level[word]:
                            next_level[nei].append(path + [nei])
        
        # Remove visited words to prevent cycles
        word_set -= set(next_level.keys())
        level = next_level
    
    return level.get(endWord, [])

Java Solution


class Solution {
    public List> findLadders(String beginWord, String endWord, List wordList) {
        Set dict = new HashSet<>(wordList);
        List> res = new ArrayList<>();
        if (!dict.contains(endWord)) {
            return res;
        }
        
        // Remove beginWord from dict if it exists
        dict.remove(beginWord);
        
        // Level by level BFS
        Map>> level = new HashMap<>();
        level.put(beginWord, Arrays.asList(Arrays.asList(beginWord)));
        
        while (!level.isEmpty() && !level.containsKey(endWord)) {
            Map>> nextLevel = new HashMap<>();
            
            for (String word : level.keySet()) {
                char[] chars = word.toCharArray();
                for (int i = 0; i < chars.length; i++) {
                    char original = chars[i];
                    for (char c = 'a'; c <= 'z'; c++) {
                        chars[i] = c;
                        String newWord = new String(chars);
                        
                        if (dict.contains(newWord)) {
                            for (List path : level.get(word)) {
                                List newPath = new ArrayList<>(path);
                                newPath.add(newWord);
                                nextLevel.computeIfAbsent(newWord, k -> new ArrayList<>())
                                        .add(newPath);
                            }
                        }
                    }
                    chars[i] = original;
                }
            }
            
            // Remove visited words
            dict.removeAll(nextLevel.keySet());
            level = nextLevel;
        }
        
        return level.getOrDefault(endWord, res);
    }
}

C++ Solution


class Solution {
public:
    vector> findLadders(string beginWord, string endWord, vector& wordList) {
        unordered_set dict(wordList.begin(), wordList.end());
        vector> res;
        if (!dict.count(endWord)) {
            return res;
        }
        
        // Remove beginWord from dict if it exists
        dict.erase(beginWord);
        
        // Level by level BFS
        unordered_map>> level;
        level[beginWord] = {{beginWord}};
        
        while (!level.empty() && !level.count(endWord)) {
            unordered_map>> nextLevel;
            
            for (const auto& [word, paths] : level) {
                string curr = word;
                for (int i = 0; i < curr.length(); i++) {
                    char original = curr[i];
                    for (char c = 'a'; c <= 'z'; c++) {
                        curr[i] = c;
                        if (dict.count(curr)) {
                            for (const auto& path : paths) {
                                vector newPath = path;
                                newPath.push_back(curr);
                                nextLevel[curr].push_back(newPath);
                            }
                        }
                    }
                    curr[i] = original;
                }
            }
            
            // Remove visited words
            for (const auto& [word, _] : nextLevel) {
                dict.erase(word);
            }
            level = nextLevel;
        }
        
        return level.count(endWord) ? level[endWord] : res;
    }
};

JavaScript Solution


/**
 * @param {string} beginWord
 * @param {string} endWord
 * @param {string[]} wordList
 * @return {string[][]}
 */
var findLadders = function(beginWord, endWord, wordList) {
    const dict = new Set(wordList);
    if (!dict.has(endWord)) {
        return [];
    }
    
    // Remove beginWord from dict if it exists
    dict.delete(beginWord);
    
    // Level by level BFS
    let level = new Map();
    level.set(beginWord, [[beginWord]]);
    
    while (level.size > 0 && !level.has(endWord)) {
        const nextLevel = new Map();
        
        for (const [word, paths] of level) {
            const chars = word.split('');
            for (let i = 0; i < chars.length; i++) {
                const original = chars[i];
                for (let c = 97; c <= 122; c++) {  // ASCII 'a' to 'z'
                    chars[i] = String.fromCharCode(c);
                    const newWord = chars.join('');
                    
                    if (dict.has(newWord)) {
                        for (const path of paths) {
                            const newPath = [...path, newWord];
                            if (!nextLevel.has(newWord)) {
                                nextLevel.set(newWord, []);
                            }
                            nextLevel.get(newWord).push(newPath);
                        }
                    }
                }
                chars[i] = original;
            }
        }
        
        // Remove visited words
        for (const word of nextLevel.keys()) {
            dict.delete(word);
        }
        level = nextLevel;
    }
    
    return level.has(endWord) ? level.get(endWord) : [];
};

C# Solution


public class Solution {
    public IList> FindLadders(string beginWord, string endWord, IList wordList) {
        HashSet dict = new HashSet(wordList);
        var result = new List>();
        if (!dict.Contains(endWord)) {
            return result;
        }
        
        // Remove beginWord from dict if it exists
        dict.Remove(beginWord);
        
        // Level by level BFS
        var level = new Dictionary>>();
        level[beginWord] = new List> { new List { beginWord } };
        
        while (level.Count > 0 && !level.ContainsKey(endWord)) {
            var nextLevel = new Dictionary>>();
            
            foreach (var word in level.Keys) {
                var chars = word.ToCharArray();
                for (int i = 0; i < chars.Length; i++) {
                    var original = chars[i];
                    for (char c = 'a'; c <= 'z'; c++) {
                        chars[i] = c;
                        var newWord = new string(chars);
                        
                        if (dict.Contains(newWord)) {
                            foreach (var path in level[word]) {
                                var newPath = new List(path);
                                newPath.Add(newWord);
                                if (!nextLevel.ContainsKey(newWord)) {
                                    nextLevel[newWord] = new List>();
                                }
                                nextLevel[newWord].Add(newPath);
                            }
                        }
                    }
                    chars[i] = original;
                }
            }
            
            // Remove visited words
            foreach (var word in nextLevel.Keys) {
                dict.Remove(word);
            }
            level = nextLevel;
        }
        
        return level.ContainsKey(endWord) ? level[endWord] : result;
    }
}

Complexity Analysis

Solution Explanation

This solution uses a level-by-level BFS approach:

Key points: