Word Ladder (LeetCode 127)
Define a transformation sequence as a chain of words beginWord -> s1 -> s2 -> ... -> sk where each step changes exactly one letter, every si (for 1 <= i <= k) appears in the dictionary wordList (though beginWord itself doesn't need to), and the final word sk equals endWord.
Given beginWord, endWord, and wordList, report how many words appear in the shortest such chain — or 0 if endWord is unreachable this way.
Worked Examples
beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]5beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]0⚖️Formal Constraints & Bounds
1 <= beginWord.length <= 10endWord.length == beginWord.length1 <= wordList.length <= 5000wordList[i].length == beginWord.lengthbeginWord,endWord, andwordList[i]consist of lowercase English letters.beginWord != endWordAll the words in
wordListare unique.
Core Insight
Always expanding the smaller of the two frontiers is what keeps bidirectional search from ever re-deriving an already-cheaper single-source exploration.
Real-World Scenario & Production Applications
Digging a tunnel from both sides through a mountain to connect two cities: two teams dig toward each other and always swap so whoever has the smaller workload digs next, cutting work down to .
Subproblems & Recurrence Decomposition4 Phases
Instead of a single queue that suffers O(b^d) combinatorial explosion, we use two sets: q1 is the digging team starting at beginWord, and q2 is the team starting at endWord. Using sets enables instant O(1) membership collision checks.
# Seed both frontiers
q1, q2 = {beginWord}, {endWord}
visited = set()Step-by-Step Execution Trace Table
Hand-Executed Step Trace (begin = "hit", end = "cog", dict = ["hot","dot","dog","lot","log","cog"])
| Step | q1 (Frontier 1) | q2 (Frontier 2) | Smaller Frontier | Intersect? | Action / State Expansion |
|---|---|---|---|---|---|
| 0 | {"hit"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hit") -> enqueues "hot". |
| 1 | {"hot"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hot") -> enqueues "dot", "lot". |
| 2 | {"dot", "lot"} (size 2) | {"cog"} (size 1) | q2 (Swap!) | No | len(q1) > len(q2) => Expand q2 ("cog") -> enqueues "dog", "log". |
| 3 | {"dog", "log"} (size 2) | {"dot", "lot"} (size 2) | q1 | Yes! | "dog" connects to "dot", "log" connects to "lot" -> Return step + 1 = 5 ✅ |
beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]5Hand-Executed Step Trace (begin = "hit", end = "cog", dict = ["hot","dot","dog","lot","log","cog"])
| Step | q1 (Frontier 1) | q2 (Frontier 2) | Smaller Frontier | Intersect? | Action / State Expansion |
|---|---|---|---|---|---|
| 0 | {"hit"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hit") -> enqueues "hot". |
| 1 | {"hot"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hot") -> enqueues "dot", "lot". |
| 2 | {"dot", "lot"} (size 2) | {"cog"} (size 1) | q2 (Swap!) | No | len(q1) > len(q2) => Expand q2 ("cog") -> enqueues "dog", "log". |
| 3 | {"dog", "log"} (size 2) | {"dot", "lot"} (size 2) | q1 | Yes! | "dog" connects to "dot", "log" connects to "lot" -> Return step + 1 = 5 ✅ |
| 1 | q1, q2 = {start}, {target} |
| 2 | visited = {start, target} |
| 3 | steps = 1 |
| 4 | while q1 and q2: |
| 5 | if len(q1) > len(q2): |
| 6 | q1, q2 = q2, q1 |
| 7 | for curr in q1: |
| 8 | for neighbor in get_neighbors(curr): |
| 9 | if neighbor in q2: |
| 10 | return steps + 1 |
| 11 | if neighbor not in visited: |
| 12 | visited.add(neighbor) |
| 13 | next_q.add(neighbor) |
| 14 | q1 = next_q |
| 15 | steps += 1 |
| 16 | return -1 |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Word Ladder transforms a vocabulary list into an unweighted graph where words are vertices, and an edge connects two words if and only if they differ by exactly one letter. Finding the shortest transformation sequence from beginWord to endWord is shortest-path BFS. However, building the graph naively by comparing all pairs takes time, which times out when . The key algorithmic leap is implicit state transition: for each word of length , substitute each character with all 26 letters of the alphabet in time and query a hash set in . Furthermore, Bidirectional BFS expands simultaneously from both ends, collapsing search complexity from to .
🏟️ The Analogy: Digging a Transcontinental Railroad Tunnel
Imagine engineers digging a 10-mile railroad tunnel through a mountain. If one team starts at the West entrance and digs toward the East, the uncertainty and search area grows like an expanding cone of volume . But if Team A digs East from the West entrance while Team B simultaneously digs West from the East entrance, each team only needs to dig 5 miles until their drills break through into each other's shafts. Two small cones have a combined volume far smaller than one giant cone ().
🔑 Why It Works
By swapping begin_set and end_set so that the smaller set is always expanded, the branching factor is tightly restricted, solving the problem in milliseconds.
All-pairs comparison : Comparing every word in
wordListwith every other word takes quadratic time. Substituting all 26 lowercase letters at each of the positions and querying a hash set takes .Not removing visited words from dictionary: Failing to delete words from
word_setupon enqueuing allows infinite word cycles between adjacent words.Target word missing from word list: If
endWord not in word_set, return0immediately before starting BFS.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Implicit word graph + one-letter-at-a-time transformation + shortest transformation sequence length.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: expanding the smaller frontier each round bounds total work by O(b^(d/2)) instead of O(b^d).
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Forgetting to check endWord in wordSet up front (immediate 0 if missing).
Expanding the larger frontier by mistake, losing the exponential speedup.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I run bidirectional BFS with two frontiers, one from beginWord and one from endWord, always expanding whichever is smaller. Each round reads every frontier word's neighbours from wildcard buckets, keys like
h*tbuilt once from the word list; hitting a word in the other frontier means the ladders met. Runs in roughly O(b^(d/2)) instead of O(b^d)."
Complexity & Mathematical Proof
O(M^2 · N)
Look at the execution steps: M = word length, N = len(wordList). Building wordmap makes M wildcard keys per word, such as h*t, each an O(M) slice: O(M^2 · N). In the search each word joins a frontier at most once (visited); expanding it builds its M keys again in O(M) each and reads their buckets, and a bucket holds at most 26 words, since they agree on every letter but one: O(M^2) per word, O(M^2 · N) in all.
O(M^2 · N) Auxiliary
Look at memory allocations: the search is a loop, so the call stack is O(1). wordmap keeps M keys of length M for each of the N words, O(M^2 · N); q1, q2, next_q and visited hold at most N words, O(M · N).
T(N, M) = N · M · O(M) + N · (M · O(M) + 26 · M) = O(M^2 · N)
Derivation Progression
N words × M keys × O(M) slice
Builds wordmap: each key such as h*t lists the words that match it.
M keys × O(M) slice + 26 words per bucket
Reads a word's neighbours from its M buckets in O(M^2) per word.
N words × O(M^2) = O(M^2 · N)
visited lets each word join a frontier at most once.
Variable Definitions
Total number of words in the word dictionary
Length of each individual word
Memory Architecture & Bounds
O(1) Iterative
O(M^2 · N) wordmap keys; O(M · N) frontiers and visited
O(1) Transformation length integer
Boundary Best / Worst Cases
even when endWord is one step away: wordmap is built from every word first.
.
traversing entire dictionary.
Graph & Tree Traversal Frontier
Senior SWE Deconstruction & Hardware Caveats
Triggers: "shortest transformation sequence", "adjacent words differ by single letter". Standard unidirectional BFS expands from beginWord with branching factor , reaching complexity . A Senior SWE immediately applies Bidirectional BFS with Frontier Swapping: expand simultaneously from both beginWord and endWord, always advancing the strictly smaller set, reducing search complexity to !
Word length , dictionary .
Comparing a word against all dictionary words costs per expansion; the wordmap buckets cut it to per word, because a bucket holds at most 26 words (they agree on every letter but one).
Word Ladder (LeetCode 127)
Define a transformation sequence as a chain of words beginWord -> s1 -> s2 -> ... -> sk where each step changes exactly one letter, every si (for 1 <= i <= k) appears in the dictionary wordList (though beginWord itself doesn't need to), and the final word sk equals endWord.
Given beginWord, endWord, and wordList, report how many words appear in the shortest such chain — or 0 if endWord is unreachable this way.
Worked Examples
beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]5beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]0⚖️Formal Constraints & Bounds
1 <= beginWord.length <= 10endWord.length == beginWord.length1 <= wordList.length <= 5000wordList[i].length == beginWord.lengthbeginWord,endWord, andwordList[i]consist of lowercase English letters.beginWord != endWordAll the words in
wordListare unique.
Core Insight
Always expanding the smaller of the two frontiers is what keeps bidirectional search from ever re-deriving an already-cheaper single-source exploration.
Real-World Scenario & Production Applications
Digging a tunnel from both sides through a mountain to connect two cities: two teams dig toward each other and always swap so whoever has the smaller workload digs next, cutting work down to .
Subproblems & Recurrence Decomposition4 Phases
Instead of a single queue that suffers O(b^d) combinatorial explosion, we use two sets: q1 is the digging team starting at beginWord, and q2 is the team starting at endWord. Using sets enables instant O(1) membership collision checks.
# Seed both frontiers
q1, q2 = {beginWord}, {endWord}
visited = set()Step-by-Step Execution Trace Table
Hand-Executed Step Trace (begin = "hit", end = "cog", dict = ["hot","dot","dog","lot","log","cog"])
| Step | q1 (Frontier 1) | q2 (Frontier 2) | Smaller Frontier | Intersect? | Action / State Expansion |
|---|---|---|---|---|---|
| 0 | {"hit"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hit") -> enqueues "hot". |
| 1 | {"hot"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hot") -> enqueues "dot", "lot". |
| 2 | {"dot", "lot"} (size 2) | {"cog"} (size 1) | q2 (Swap!) | No | len(q1) > len(q2) => Expand q2 ("cog") -> enqueues "dog", "log". |
| 3 | {"dog", "log"} (size 2) | {"dot", "lot"} (size 2) | q1 | Yes! | "dog" connects to "dot", "log" connects to "lot" -> Return step + 1 = 5 ✅ |
beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]5Hand-Executed Step Trace (begin = "hit", end = "cog", dict = ["hot","dot","dog","lot","log","cog"])
| Step | q1 (Frontier 1) | q2 (Frontier 2) | Smaller Frontier | Intersect? | Action / State Expansion |
|---|---|---|---|---|---|
| 0 | {"hit"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hit") -> enqueues "hot". |
| 1 | {"hot"} (size 1) | {"cog"} (size 1) | q1 | No | Expand q1 ("hot") -> enqueues "dot", "lot". |
| 2 | {"dot", "lot"} (size 2) | {"cog"} (size 1) | q2 (Swap!) | No | len(q1) > len(q2) => Expand q2 ("cog") -> enqueues "dog", "log". |
| 3 | {"dog", "log"} (size 2) | {"dot", "lot"} (size 2) | q1 | Yes! | "dog" connects to "dot", "log" connects to "lot" -> Return step + 1 = 5 ✅ |
| 1 | q1, q2 = {start}, {target} |
| 2 | visited = {start, target} |
| 3 | steps = 1 |
| 4 | while q1 and q2: |
| 5 | if len(q1) > len(q2): |
| 6 | q1, q2 = q2, q1 |
| 7 | for curr in q1: |
| 8 | for neighbor in get_neighbors(curr): |
| 9 | if neighbor in q2: |
| 10 | return steps + 1 |
| 11 | if neighbor not in visited: |
| 12 | visited.add(neighbor) |
| 13 | next_q.add(neighbor) |
| 14 | q1 = next_q |
| 15 | steps += 1 |
| 16 | return -1 |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Word Ladder transforms a vocabulary list into an unweighted graph where words are vertices, and an edge connects two words if and only if they differ by exactly one letter. Finding the shortest transformation sequence from beginWord to endWord is shortest-path BFS. However, building the graph naively by comparing all pairs takes time, which times out when . The key algorithmic leap is implicit state transition: for each word of length , substitute each character with all 26 letters of the alphabet in time and query a hash set in . Furthermore, Bidirectional BFS expands simultaneously from both ends, collapsing search complexity from to .
🏟️ The Analogy: Digging a Transcontinental Railroad Tunnel
Imagine engineers digging a 10-mile railroad tunnel through a mountain. If one team starts at the West entrance and digs toward the East, the uncertainty and search area grows like an expanding cone of volume . But if Team A digs East from the West entrance while Team B simultaneously digs West from the East entrance, each team only needs to dig 5 miles until their drills break through into each other's shafts. Two small cones have a combined volume far smaller than one giant cone ().
🔑 Why It Works
By swapping begin_set and end_set so that the smaller set is always expanded, the branching factor is tightly restricted, solving the problem in milliseconds.
All-pairs comparison : Comparing every word in
wordListwith every other word takes quadratic time. Substituting all 26 lowercase letters at each of the positions and querying a hash set takes .Not removing visited words from dictionary: Failing to delete words from
word_setupon enqueuing allows infinite word cycles between adjacent words.Target word missing from word list: If
endWord not in word_set, return0immediately before starting BFS.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Implicit word graph + one-letter-at-a-time transformation + shortest transformation sequence length.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: expanding the smaller frontier each round bounds total work by O(b^(d/2)) instead of O(b^d).
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Forgetting to check endWord in wordSet up front (immediate 0 if missing).
Expanding the larger frontier by mistake, losing the exponential speedup.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I run bidirectional BFS with two frontiers, one from beginWord and one from endWord, always expanding whichever is smaller. Each round reads every frontier word's neighbours from wildcard buckets, keys like
h*tbuilt once from the word list; hitting a word in the other frontier means the ladders met. Runs in roughly O(b^(d/2)) instead of O(b^d)."
Complexity & Mathematical Proof
O(M^2 · N)
Look at the execution steps: M = word length, N = len(wordList). Building wordmap makes M wildcard keys per word, such as h*t, each an O(M) slice: O(M^2 · N). In the search each word joins a frontier at most once (visited); expanding it builds its M keys again in O(M) each and reads their buckets, and a bucket holds at most 26 words, since they agree on every letter but one: O(M^2) per word, O(M^2 · N) in all.
O(M^2 · N) Auxiliary
Look at memory allocations: the search is a loop, so the call stack is O(1). wordmap keeps M keys of length M for each of the N words, O(M^2 · N); q1, q2, next_q and visited hold at most N words, O(M · N).
T(N, M) = N · M · O(M) + N · (M · O(M) + 26 · M) = O(M^2 · N)
Derivation Progression
N words × M keys × O(M) slice
Builds wordmap: each key such as h*t lists the words that match it.
M keys × O(M) slice + 26 words per bucket
Reads a word's neighbours from its M buckets in O(M^2) per word.
N words × O(M^2) = O(M^2 · N)
visited lets each word join a frontier at most once.
Variable Definitions
Total number of words in the word dictionary
Length of each individual word
Memory Architecture & Bounds
O(1) Iterative
O(M^2 · N) wordmap keys; O(M · N) frontiers and visited
O(1) Transformation length integer
Boundary Best / Worst Cases
even when endWord is one step away: wordmap is built from every word first.
.
traversing entire dictionary.
Graph & Tree Traversal Frontier
Senior SWE Deconstruction & Hardware Caveats
Triggers: "shortest transformation sequence", "adjacent words differ by single letter". Standard unidirectional BFS expands from beginWord with branching factor , reaching complexity . A Senior SWE immediately applies Bidirectional BFS with Frontier Swapping: expand simultaneously from both beginWord and endWord, always advancing the strictly smaller set, reducing search complexity to !
Word length , dictionary .
Comparing a word against all dictionary words costs per expansion; the wordmap buckets cut it to per word, because a bucket holds at most 26 words (they agree on every letter but one).