Problem
You are given two words and a dictionary of valid words. Starting from the first word, find the length of the shortest sequence of one-letter substitutions that transforms it into the second word, where every intermediate word (and the final word) must exist in the dictionary.
- Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
- Output: 5
- Explanation: "hit" โ "hot" โ "dot" โ "dog" โ "cog" is the shortest valid 5-word sequence
If no such sequence exists โ either because endWord isn't in the dictionary or no chain of single-letter changes can reach it โ return 0.
Intuition
This is a shortest-path problem disguised as a word puzzle. Each word is a node, and two nodes share an edge when they differ by exactly one letter. BFS is the right tool because it explores all words reachable in 1 step before words reachable in 2 steps, so the first time you reach endWord is guaranteed to be along the shortest path. The key efficiency trick is generating each word's neighbors on the fly โ try all 26 letters at each character position โ rather than pre-building the full graph, which would cost O(Nยฒ) pairwise comparisons.
Solution โ BFS on Implicit Word Graph
Use level-by-level BFS from beginWord, generating neighbors by substituting each character with 'a' through 'z'. Words are marked visited by removing them from the set when first seen, so they're never re-enqueued.
- Convert
wordListto a hash set; return 0 immediately ifendWordis absent. - Push
beginWordinto the queue and remove it from the set (it's already visited). - At each BFS level, snapshot the queue size to process exactly one layer at a time.
- For each word in the layer, try all L ร 26 single-character substitutions.
- If a substitution matches
endWord, returnsteps + 1. - If a substitution is in the word set, remove it (marking it visited) and enqueue it.
1from collections import deque
2
3class Solution:
4 def ladderLength(self, beginWord: str, endWord: str, wordList: list[str]) -> int:
5 word_set = set(wordList)
6 if endWord not in word_set:
7 return 0
8
9 queue = deque([beginWord])
10 word_set.discard(beginWord) # removal from set acts as visited marker
11 steps = 1
12
13 while queue:
14 for _ in range(len(queue)):
15 word = queue.popleft()
16 for i in range(len(word)):
17 for char in 'abcdefghijklmnopqrstuvwxyz':
18 next_word = word[:i] + char + word[i + 1:]
19 if next_word == endWord:
20 return steps + 1
21 if next_word in word_set:
22 word_set.discard(next_word)
23 queue.append(next_word)
24 steps += 1
25
26 return 0Time: O(N ร Lยฒ) โ for each of N words we try 26 ร L substitutions, and each hash set lookup hashes a string of length L, so O(L) per lookup.
Space: O(N ร L) โ the word set and BFS queue together hold at most N words of length L.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| BFS on Implicit Word Graph | O(N ร Lยฒ) | O(N ร L) | Finding the shortest word transformation chain in a fixed dictionary |
Common Mistakes
- Not checking whether
endWordis in the dictionary first โ if it's absent no path can exist; without this check, BFS will traverse every reachable word before returning 0, wasting all of it. - Forgetting to mark
beginWordas visited โ ifbeginWordalso appears inwordList, BFS can circle back to it; removing it from the set at the start breaks that cycle. - Marking words visited only when dequeuing, not when enqueuing โ the same word can be discovered via multiple neighbors in the same BFS layer and enqueued multiple times, inflating queue size and producing wrong step counts.
- Off-by-one on the step counter โ the sequence length includes
beginWorditself, so the count starts at 1; when you first discoverendWordas a neighbor you returnsteps + 1, notsteps. - Allocating new strings in a tight loop in Java/C++ โ Python's string slicing is unavoidable at O(L) per substitution, but in Java and C++ you can mutate a single character in a
char[]/stringand restore it after each attempt, avoiding N ร L ร 26 string allocations.
Related Problems
01-matrixโ multi-source BFS where every 0-cell is a simultaneous starting point; same layer-by-layer expansion to find shortest distancesnakes-and-laddersโ BFS for minimum dice rolls on a board; each board position is a node, legal moves are edgesevaluate-divisionโ builds a weighted graph between variables and answers ratio queries with BFS; same idea of traversing an implicit graphclone-graphโ BFS traversal of an unknown graph by following edges as they're discovered, rather than pre-enumerating neighborsnumber-of-islandsโ BFS/DFS on a grid's implicit adjacency graph; same pattern of marking cells visited by erasing them in place