LeetCode #127 Hard

Word Ladder

Find the length of the shortest transformation sequence from beginWord to endWord, changing one letter at a time through words in wordList.

bfsimplicit-graphstrings
Open on LeetCode ↗
02

Intuition

It is tempting to compare every word against every other word to decide which pairs are one letter apart, but that is O(n^2 L) before the BFS even starts. Instead, never build the graph at all: for the current word, try swapping in each of the 26 letters at each position, and check whether the result is in the word set. That generates exactly the neighbors that matter, on demand, in O(L 26) per word. The graph is implicit - it exists only as a rule for generating edges, computed lazily as BFS needs them.

How to spot this pattern

BFS over an implicit graph — the words are nodes and a single-letter change is an edge, but the edges are generated on demand rather than stored. Removing a word from the set as it's enqueued is the visited marker, and it doubles as the deduplication.

03

Approach

1

Guard the target

If endWord is not in wordList, no sequence of legal one-letter swaps can ever land on it, so return 0 immediately.

2

Generate neighbors on demand

From the current word, for each position and each of the 26 letters, build the candidate word. If it is in the word set and unvisited, it is a genuine neighbor - no precomputed adjacency list needed.

3

BFS layer = transformation count

Track distance from beginWord (starting at 1). The first time endWord is generated, its distance is the answer, since BFS explores shortest sequences first.

04

Solution & live demo

1class Solution:
2 def ladderLength(self, beginWord, endWord, wordList):
3 from collections import deque
4 words = set(wordList)
5 if endWord not in words:
6 return 0
7 q = deque([(beginWord, 1)])
8 words.discard(beginWord)
9 while q:
10 word, d = q.popleft()
11 if word == endWord:
12 return d
13 for i in range(len(word)):
14 for ch in 'abcdefghijklmnopqrstuvwxyz':
15 cand = word[:i] + ch + word[i+1:]
16 if cand in words:
17 words.discard(cand)
18 q.append((cand, d+1))
19 return 0
05

Common pitfalls

Comparing every pair of words to build edges

✗ Wrong
for a in words:
    for b in words:
        if differs_by_one(a, b): adj[a].append(b)
✓ Right
for i in range(len(word)):
    for ch in 'abcdefghijklmnopqrstuvwxyz':

That's O(N²·L) in the word count. Generating the 26·L neighbours of a word and testing set membership is O(26·L) per word, independent of how many words exist.

Not removing words when enqueuing

✗ Wrong
if cand in words:
    q.append((cand, d + 1))
✓ Right
if cand in words:
    words.discard(cand)
    q.append((cand, d + 1))

The same word would be reached from several predecessors and enqueued repeatedly, blowing up the queue exponentially. Since BFS finds the shortest route first, later arrivals can be discarded outright.

Skipping the endWord membership check

✗ Wrong
q = deque([(beginWord, 1)])
✓ Right
if endWord not in words:
    return 0

If the target isn't in the word list no transformation sequence exists, and the BFS would exhaust the entire reachable set before returning 0. The check is immediate and states the precondition.

06

Edge cases

endWord not in wordList

Return 0 right away, no BFS needed.

beginWord already equals endWord

Not a valid LeetCode input, but would resolve trivially.

wordList has unrelated words

They are simply never generated as neighbors and never visited.

No transformation path exists

BFS queue empties without reaching endWord; return 0.

07

Complexity

Time
O(n * L * 26)
Space
O(n * L)
n = wordList size, L = word length; no pairwise comparisons ever built.