Word Ladder II
HARDDescription
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
sifor1 <= i <= kis inwordList. Note thatbeginWorddoes not need to be inwordList. 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 <= 5endWord.length == beginWord.length1 <= wordList.length <= 500wordList[i].length == beginWord.lengthbeginWord,endWord, andwordList[i]consist of lowercase English letters.beginWord != endWord- All the words in
wordListare unique. - The sum of all shortest transformation sequences does not exceed
105.
Approaches
Checkout 3 different approaches to solve Word Ladder II. Click on different approaches to view the approach and algorithm in detail.
Brute-Force BFS with Path Tracking
This approach uses a Breadth-First Search (BFS) to explore the word transformations level by level. To reconstruct the paths, the entire path from the beginWord to the current word is stored in the BFS queue. When a path reaches the endWord, it's added to a result list. The search continues only up to the level where the first shortest path is found to ensure all collected paths are of minimum length.
Algorithm
- Initialize a queue to store paths (lists of words). Add the initial path
[beginWord]. - Convert
wordListto aSetfor efficient O(1) lookups. - Perform a level-by-level Breadth-First Search (BFS).
- In each level, process all paths currently in the queue.
- For each path, take the last word and generate all its one-letter-different neighbors.
- If a neighbor is the
endWord, a shortest path is found. Add this complete path to the results list and set a flagfoundto true. - If a neighbor is a valid word in the dictionary and hasn't been visited in the current level, create a new path by appending the neighbor and add it to the queue. Keep track of these newly visited words in a
visitedInLevelset. - After a level is fully processed, if
foundis true, terminate the search. - Otherwise, remove all words in
visitedInLevelfrom the main dictionary set to prevent cycles and longer paths. Continue to the next level.
This method directly translates the problem into a pathfinding search on a graph. We treat each word as a node and a single-letter difference as an edge.
The core of this approach is a BFS, which naturally finds the shortest path in terms of the number of transformations (edges). The main challenge is to find all shortest paths. To do this, we store the entire transformation sequence (path) in our BFS queue instead of just the current word.
Here's the process:
- We start with a queue containing a single path:
[beginWord]. - We process the search level by level. For each level, we dequeue all paths of that length.
- For each path, we look at the last word and find all its valid neighbors (one letter difference, present in the
wordList). - If a neighbor is the
endWord, we've found a shortest path. We add the full path to our results. We make a note that we've found a solution, which means we don't need to explore any deeper levels. - If a neighbor is another valid word, we create a new path by extending the current one and add it to the queue for the next level's processing.
- A crucial step for correctness and efficiency is to avoid cycles and moving backward. We do this by keeping track of all words visited at the current level. After the level is complete, we remove all these words from our dictionary set. This ensures that in subsequent (longer) paths, we don't revisit these words.
While conceptually simple, this approach is highly inefficient as the number of paths can grow exponentially, leading to excessive memory and time consumption.
class Solution {
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
List<List<String>> results = new ArrayList<>();
Set<String> dict = new HashSet<>(wordList);
if (!dict.contains(endWord)) {
return results;
}
Queue<List<String>> queue = new LinkedList<>();
List<String> initialPath = new ArrayList<>();
initialPath.add(beginWord);
queue.offer(initialPath);
Set<String> visitedInLevel = new HashSet<>();
visitedInLevel.add(beginWord);
boolean found = false;
while (!queue.isEmpty()) {
int levelSize = queue.size();
// Words visited in the current level are removed from the main dict
// after the level is processed to avoid cycles and longer paths.
for (String word : visitedInLevel) {
dict.remove(word);
}
visitedInLevel.clear();
if (found) break; // Stop if shortest paths were found in the previous level
for (int i = 0; i < levelSize; i++) {
List<String> currentPath = queue.poll();
String lastWord = currentPath.get(currentPath.size() - 1);
char[] chars = lastWord.toCharArray();
for (int j = 0; j < chars.length; j++) {
char originalChar = chars[j];
for (char c = 'a'; c <= 'z'; c++) {
chars[j] = c;
String newWord = new String(chars);
if (dict.contains(newWord)) {
List<String> newPath = new ArrayList<>(currentPath);
newPath.add(newWord);
if (newWord.equals(endWord)) {
results.add(newPath);
found = true;
} else {
visitedInLevel.add(newWord);
queue.offer(newPath);
}
}
}
chars[j] = originalChar; // backtrack
}
}
}
return results;
}
}
Complexity Analysis
Pros and Cons
- Conceptually simple and a direct implementation of the problem statement.
- Directly builds the final paths during the search process.
- Extremely high memory usage because the queue stores complete paths, which can grow exponentially.
- High time complexity due to redundant computations. If multiple paths reach the same intermediate word, the search from that word is repeated for each path.
- Very likely to cause 'Time Limit Exceeded' or 'Memory Limit Exceeded' errors on non-trivial test cases.
Code Solutions
Checking out 2 solutions in different languages for Word Ladder II. Click on different languages to view the code.
Video Solution
Watch the video walkthrough for Word Ladder II
Similar Questions
5 related questions you might find useful
Algorithms:
Patterns:
Data Structures:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.