Word Ladder II
HardPrompt
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
3 approaches with complexity analysis and trade-offs.
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.
Walkthrough
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
Time
O(N * L * b^d)
Space
O(N * d * b^d)
Trade-offs
Pros
Conceptually simple and a direct implementation of the problem statement.
Directly builds the final paths during the search process.
Cons
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.
This is a more optimized, two-pass approach. The first pass uses Breadth-First Search (BFS) to find the shortest distance from beginWord to all other words. During this BFS, it constructs a directed graph that only contains the edges that are part of any shortest path. The second pass uses Depth-First Search (DFS) on this graph, starting from beginWord, to reconstruct all the shortest paths to endWord.
Algorithm
Pass 1: BFS to Build Shortest Path Graph
- Initialize a
distancesmap to store the shortest distance frombeginWordto each word. - Initialize an adjacency list map
adjto store the directed graph of shortest paths. - Use a queue and perform a level-by-level BFS starting from
beginWord. - For each word
uat the current level, find all its neighborsv. - If
vhas not been visited (!distances.containsKey(v)), update its distance, add an edgeu -> vtoadj, and enqueuev. - If
vhas been visited but is on the next level (distances.get(v) == distances.get(u) + 1), it means we found another shortest path tov. Add the edgeu -> vtoadj.
Pass 2: DFS to Reconstruct Paths
- Initialize an empty list for results and a list for the current path.
- Start a recursive DFS from
beginWord. - The DFS function explores the
adjgraph. When it reachesendWord, the current path is a valid shortest path and is added to the results. - Use backtracking to explore all possible paths from each node in the
adjgraph.
Walkthrough
This approach decouples the problem into two distinct phases to overcome the inefficiencies of the naive method.
Phase 1: BFS for Distances and Graph Building
Instead of storing full paths, the BFS pass focuses on finding the shortest distance from beginWord to every other reachable word. It simultaneously builds a directed acyclic graph (DAG) representing all shortest path connections.
- We use a
distancesmap (String -> Integer) to keep track of the shortest level/distance for each word frombeginWord. This also serves to mark words as visited. - We use an adjacency list (
Map<String, List<String>> adj) to store the DAG. An edgeu -> vis added to this graph only if moving fromutovfollows a shortest path (i.e.,distance(v) = distance(u) + 1). - The BFS proceeds level by level. When considering a word
u, we generate its neighborsv. Ifvis on the next level, we add the edgeu -> vto ouradjmap. This way, we capture all branches that contribute to a shortest path.
Phase 2: DFS for Path Reconstruction
Once the BFS completes, we have a compact representation (adj) of all shortest paths. Now, we can efficiently find all the actual path sequences.
- We perform a standard Depth-First Search (DFS) starting from
beginWordand traversing only the edges present in ouradjgraph. - We maintain a
currentPathlist. Whenever the DFS reaches theendWord, we have found a complete shortest path, which we add to our final results. - Backtracking is used to explore all possible routes through the
adjgraph.
This method is significantly more efficient because it avoids storing redundant path information during the initial exploration phase.
class Solution { private Map<String, List<String>> adj = new HashMap<>(); private List<List<String>> results = new ArrayList<>(); private List<String> path = new ArrayList<>(); public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { Set<String> dict = new HashSet<>(wordList); if (!dict.contains(endWord)) { return results; } // Step 1: BFS to find shortest path lengths and build adj list bfs(beginWord, endWord, dict); // Step 2: DFS to find all paths using the adj list path.add(beginWord); dfs(beginWord, endWord); return results; } private void bfs(String beginWord, String endWord, Set<String> dict) { Map<String, Integer> distances = new HashMap<>(); Queue<String> queue = new LinkedList<>(); queue.offer(beginWord); distances.put(beginWord, 0); boolean found = false; while (!queue.isEmpty()) { int size = queue.size(); if (found) break; for (int i = 0; i < size; i++) { String currentWord = queue.poll(); int currentDist = distances.get(currentWord); char[] chars = currentWord.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)) { if (!distances.containsKey(newWord)) { // First time visiting this word distances.put(newWord, currentDist + 1); if (newWord.equals(endWord)) found = true; queue.offer(newWord); adj.computeIfAbsent(currentWord, k -> new ArrayList<>()).add(newWord); } else if (distances.get(newWord) == currentDist + 1) { // Found another path of the same shortest length adj.computeIfAbsent(currentWord, k -> new ArrayList<>()).add(newWord); } } } chars[j] = originalChar; // backtrack } } } } private void dfs(String currentWord, String endWord) { if (currentWord.equals(endWord)) { results.add(new ArrayList<>(path)); return; } if (adj.containsKey(currentWord)) { for (String nextWord : adj.get(currentWord)) { path.add(nextWord); dfs(nextWord, endWord); path.remove(path.size() - 1); // backtrack } } }}Complexity
Time
O(N * L^2)
Space
O(N * L)
Trade-offs
Pros
Much more efficient in time and space compared to the naive BFS.
Avoids storing entire paths in the BFS queue, preventing exponential memory growth.
Systematically finds the shortest path graph structure before reconstructing the paths.
Cons
More complex to implement due to the two-pass nature (BFS then DFS).
The initial BFS might still explore a large portion of the graph if the
endWordis far away or the graph is dense.
This approach optimizes the graph-building phase of the previous method by using a bidirectional BFS. Instead of searching only from beginWord outwards, we simultaneously search from endWord backwards. The search stops when the two search frontiers meet. This significantly prunes the search space, as the number of nodes explored is proportional to b^(d/2) + b^(d/2) instead of b^d (where b is the branching factor and d is the path length). After the shortest path graph is built, a DFS is used, as before, to reconstruct all the paths.
Algorithm
Pass 1: Bidirectional BFS to Build Graph
- Initialize two sets,
beginSetandendSet, for the two search frontiers, starting withbeginWordandendWord. - Initialize an adjacency list map
adjto store the directed graph. - In a loop, always expand the smaller of the two sets to keep the search balanced.
- When expanding a word
ufrombeginSet, generate its neighborsv. - If a neighbor
vis found in theendSet, a shortest path has been found. Add the corresponding edge toadjand set afoundflag. - If a neighbor
vis a new word (not yet visited by either search), add it to anextLevelSetand add the edge toadj. - Be careful to maintain the correct direction of edges in
adj(e.g., always from thebeginWordside towards theendWordside). - Once a path is found, finish the current level to find all meeting points at the same shortest distance, then stop the BFS.
Pass 2: DFS to Reconstruct Paths
- This pass is identical to the one in the previous approach. Use the
adjgraph built by the bidirectional BFS and run a DFS frombeginWordto find and store all paths.
Walkthrough
This is the most optimal solution, which improves upon the BFS+DFS approach by making the initial graph-building phase faster.
Phase 1: Bidirectional BFS
The key idea is that searching from both the start and end points simultaneously and meeting in the middle is much faster than searching the entire distance from one end. The search space of a BFS grows exponentially with its radius. By using two searches of radius d/2, the total explored space 2 * b^(d/2) is much smaller than b^d for a single search.
- We maintain two search frontiers,
beginSetandendSet. - In each step of the loop, we choose to expand the smaller of the two sets. This ensures the two search radii remain roughly equal, maximizing the efficiency gain.
- As we expand, we build the same
adjgraph as in the previous approach, containing only edges on shortest paths. - The search terminates once a word from one set is found to be a neighbor of a word in the other set. We must complete the current level to find all such meeting points that correspond to the shortest path length.
Phase 2: DFS for Path Reconstruction
This phase remains unchanged. The bidirectional BFS produces the exact same shortest-path DAG (adj map) as the unidirectional BFS, just more quickly. We then apply the same DFS traversal on this graph starting from beginWord to enumerate all the paths.
class Solution { private Map<String, List<String>> adj = new HashMap<>(); private List<List<String>> results = new ArrayList<>(); private List<String> path = new ArrayList<>(); public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { Set<String> dict = new HashSet<>(wordList); if (!dict.contains(endWord)) { return results; } // Step 1: Bidirectional BFS to build the graph biBfs(beginWord, endWord, dict); // Step 2: DFS to reconstruct paths path.add(beginWord); dfs(beginWord, endWord); return results; } private void biBfs(String beginWord, String endWord, Set<String> dict) { Set<String> beginSet = new HashSet<>(); beginSet.add(beginWord); Set<String> endSet = new HashSet<>(); endSet.add(endWord); boolean found = false; boolean reversed = false; // false: forward (begin->end), true: backward (end->begin) while (!beginSet.isEmpty() && !found) { // Words in the current frontier are effectively visited at this level dict.removeAll(beginSet); Set<String> nextLevelSet = new HashSet<>(); for (String word : beginSet) { char[] chars = word.toCharArray(); for (int i = 0; i < chars.length; i++) { char originalChar = chars[i]; for (char c = 'a'; c <= 'z'; c++) { chars[i] = c; String newWord = new String(chars); String parent = reversed ? newWord : word; String child = reversed ? word : newWord; if (endSet.contains(newWord)) { found = true; adj.computeIfAbsent(parent, k -> new ArrayList<>()).add(child); } else if (dict.contains(newWord)) { nextLevelSet.add(newWord); adj.computeIfAbsent(parent, k -> new ArrayList<>()).add(child); } } chars[i] = originalChar; } } beginSet = nextLevelSet; if (beginSet.size() > endSet.size()) { Set<String> temp = beginSet; beginSet = endSet; endSet = temp; reversed = !reversed; } } } private void dfs(String currentWord, String endWord) { if (currentWord.equals(endWord)) { results.add(new ArrayList<>(path)); return; } if (adj.containsKey(currentWord)) { for (String nextWord : adj.get(currentWord)) { path.add(nextWord); dfs(nextWord, endWord); path.remove(path.size() - 1); } } }}Complexity
Time
O(N * L^2)
Space
O(N * L)
Trade-offs
Pros
The most efficient approach in terms of time complexity for finding the shortest path structure.
Significantly reduces the search space compared to a unidirectional BFS, making it much faster in practice.
Cons
The implementation is the most complex of the three approaches.
Care must be taken to correctly handle the meeting of the two searches and build the directed graph for the DFS pass, especially with respect to edge direction.
Solutions
Solution
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 ; } } } ////// class Solution { private List < List < String >> ans ; private Map < String , Set < String >> prev ; public List < List < String >> findLadders ( String beginWord , String endWord , List < String > wordList ) { ans = new ArrayList <>(); Set < String > words = new HashSet <>( wordList ); if (! words . contains ( endWord )) { return ans ; } words . remove ( beginWord ); Map < String , Integer > dist = new HashMap <>(); dist . put ( beginWord , 0 ); prev = new HashMap <>(); Queue < String > q = new ArrayDeque <>(); q . offer ( beginWord ); boolean found = false ; int step = 0 ; while (! q . isEmpty () && ! found ) { ++ step ; for ( int i = q . size (); i > 0 ; -- i ) { String p = q . poll (); char [] chars = p . toCharArray (); for ( int j = 0 ; j < chars . length ; ++ j ) { char ch = chars [ j ]; for ( char k = 'a' ; k <= 'z' ; ++ k ) { chars [ j ] = k ; String t = new String ( chars ); if ( dist . getOrDefault ( t , 0 ) == step ) { prev . get ( t ). add ( p ); } if (! words . contains ( t )) { continue ; } prev . computeIfAbsent ( t , key -> new HashSet <>()). add ( p ); words . remove ( t ); q . offer ( t ); dist . put ( t , step ); if ( endWord . equals ( t )) { found = true ; } } chars [ j ] = ch ; } } } if ( found ) { Deque < String > path = new ArrayDeque <>(); path . add ( endWord ); dfs ( path , beginWord , endWord ); } return ans ; } private void dfs ( Deque < String > path , String beginWord , String cur ) { if ( cur . equals ( beginWord )) { ans . add ( new ArrayList <>( path )); return ; } for ( String precursor : prev . get ( cur )) { path . addFirst ( precursor ); dfs ( path , beginWord , precursor ); path . removeFirst (); } } }''' remove() same as discard() >>> a = set([11,22,33]) >>> a.remove(22) >>> a {33, 11} >>> a = set([11,22,33]) >>> a.discard(22) >>> a {33, 11} >>> a=set([1,2,3]) >>> a.discard(555) >>> a.remove(555) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 555 ''' class Solution : def findLadders ( self , beginWord : str , endWord : str , wordList : List [ str ] ) -> List [ List [ str ]]: # endWord to beginWord # better than begin to end, too many paths not in final result def dfs ( path , cur ): if cur == beginWord : ans . append ( path [:: - 1 ]) return for precursor in prev [ cur ]: path . append ( precursor ) dfs ( path , precursor ) path . pop () ans = [] words = set ( wordList ) if endWord not in words : return ans # no exception if beginWord not in set words . discard ( beginWord ) dist = { beginWord : 0 } prev = defaultdict ( set ) q = deque ([ beginWord ]) found = False step = 0 while q and not found : step += 1 for _ in range ( len ( q ), 0 , - 1 ): p = q . popleft () s = list ( p ) for i in range ( len ( s )): ch = s [ i ] for j in range ( 26 ): s [ i ] = chr ( ord ( 'a' ) + j ) t = '' . join ( s ) if dist . get ( t , 0 ) == step : prev [ t ]. add ( p ) # repeated 3 lines below if t not in words : # if above '== step' met, then t must be removed from words[] from previous iterations continue prev [ t ]. add ( p ) # repeated 3 lines above words . discard ( t ) q . append ( t ) dist [ t ] = step if endWord == t : found = True s [ i ] = ch if found : path = [ endWord ] dfs ( path , endWord ) return ans ############ 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 resVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.