The problem
You get a begin word, an end word and a list of allowed words wordList. A transformation changes exactly one letter at a time, and every intermediate word must be in wordList (begin itself does not have to be). Return the number of words in the shortest sequence from begin to end, counting both ends, or 0 if no sequence exists.
Input: begin = "hit", end = "cog"
wordList = ["hot", "dot", "dog", "lot", "log", "cog"]
Output: 5hit -> hot -> dot -> dog -> cog uses 5 words, and there is no shorter chain.
How to think about it
Start with brute force. Try every possible sequence with backtracking: from the current word, recurse into every unused word that differs by one letter, and keep the shortest chain that reaches end. The number of paths explodes exponentially.
The key insight: this is a shortest path in a graph. Make each word a node, and join two words with an edge if they differ in exactly one position. “Fewest transformations” is “fewest edges”. Every edge costs the same, so the right tool is BFS: the first time BFS reaches end, it got there by the shortest route.
Don’t build the graph up front. Comparing every pair of words is O(N² · L). Instead, generate neighbours on the fly: for each position in the current word, try all 26 letters and keep the results that are in the word set. That is L · 26 candidates per word, each checked in O(1) average with a set (plus O(L) to build the string).
Remove a word from the set when you queue it. That marks it visited, so it can’t be queued again by a longer path. It also shrinks the set as the search goes on.
Which pattern is this? BFS on an implicit graph. The tell is “minimum number of steps”, where each step is a small change to a state.
Approach
- Put
wordListin a setwords. Ifendisn’t in it, return 0. - Start a queue with
(begin, 1). - Pop
(word, d). Ifword == end, returnd. - For each position
iand each letterch, formnxt. Ifnxtis inwords, remove it and push(nxt, d + 1). - If the queue empties, return 0.
Solution
from collections import deque
def ladder_length(begin, end, wordList):
words = set(wordList) # O(1) membership; doubles as "unvisited"
if end not in words: return 0
q = deque([(begin, 1)]) # (word, words in the chain so far)
while q:
word, d = q.popleft()
if word == end: return d # BFS: first arrival is the shortest
for i in range(len(word)):
for ch in "abcdefghijklmnopqrstuvwxyz":
nxt = word[:i] + ch + word[i+1:] # every one-letter edit
if nxt in words:
words.remove(nxt); q.append((nxt, d + 1)) # mark visited when queued
return 0
# ladder_length("hit","cog",["hot","dot","dog","lot","log","cog"]) -> 5Dry run
On the example:
| Pop | d |
Neighbours found and queued |
|---|---|---|
| hit | 1 | hot |
| hot | 2 | dot, lot |
| dot | 3 | dog |
| lot | 3 | log |
| dog | 4 | cog |
| log | 4 | — (cog already taken) |
| cog | 5 | it’s end: return 5 |
Complexity
- Time: O(N · L² · 26) strictly, often quoted as O(N · L · 26). Each of the N words is popped at most once and tries
L · 26candidates. Building each candidate string is O(L), which gives the extra factor of L; with short words it’s treated as a constant. - Space: O(N · L) for the word set and the queue (O(N) words of length L).
Edge cases and pitfalls
endnot inwordList: return 0 straight away.- No path:
ladder_length("hit", "cog", ["hot","dot","dog","lot","log"])returns 0 becausecogis missing; ifcogis present but unreachable, the queue empties and it also returns 0. - Counting edges instead of words. The answer counts words, so
beginstarts at 1. - Marking visited on pop instead of on push can queue the same word many times and blow up the run time.
- DFS finds a path, not the shortest one. For a big speed-up, run BFS from both ends at once (bidirectional BFS).
Related problems
- Rotting Oranges (LeetCode 994): BFS distances, level by level, on a grid.
- Minimum Depth of Binary Tree (LeetCode 111): BFS that returns at the first target it reaches.
- Network Delay Time (LeetCode 743): shortest paths when edges have weights, where BFS gives way to Dijkstra.
- 1.Average of Levels in Binary Tree (LeetCode 637)
- 2.Minimum Depth of Binary Tree (LeetCode 111)
- 3.Cousins in Binary Tree (LeetCode 993)
- 4.Binary Tree Level Order Traversal (LeetCode 102)
- 5.Binary Tree Level Order Traversal II (LeetCode 107)
- 6.Binary Tree Right Side View (LeetCode 199)
- 7.Binary Tree Zigzag Level Order Traversal (LeetCode 103)
- 8.Populating Next Right Pointers in Each Node (LeetCode 116)
- 9.Rotting Oranges (LeetCode 994)
- 10.Word Ladder (LeetCode 127)