Talentd
FleetCode
Make and follow sheets.Sign in

Word Ladder II solution and explanation

HardBacktrackingBreadth-First SearchHash TableString7 min read27.7% acceptanceUpdated 9 Apr 2026
Word Ladder 2 | Leetcode #126 | BFS + DFSTechdose · 24:13 · 53K viewsWatch on YouTube
9 more videos on this problem
  1. Word Ladder 2 | Leetcode #126 | BFS + DFSTechdose · 24:13 · 53K views
  2. LeetCode 126. Word Ladder II Explanation and Solutionhappygirlzt · 20:44 · 12K views
  3. LeetCode 126. Word Ladder IIHappy Coding · 14:48 · 7K views
  4. Word Ladder II | Strings | BFS | 126 LeetCode | Day 24CodeWithSunny · 27:31 · 1K views
  5. Daily Leetcode #866: Aug 14, 2022 - 126. Word Ladder IIProgramming Live with Larry · 22:38 · 1K views
  6. Word Ladder II | Leetcode 126 | Word Ladder | Leetcode 127 | Live coding sessionCoding Decoded · 0:52 · 1K views
  7. 126. Word Ladder II (Leetcode Hard)Programming Live with Larry · 1:08:54 · 1K views
  8. Word Ladder 2 (LeetCode 126 - Hard)TechMockInterview · 34:39 · 1K views
  9. 126. Word Ladder II | English | HardSimple Code - 단순코드 · 15:48 · 619 views
  10. Daily Leetcode #480: Jul 24, 2021 - 126. Word Ladder IIProgramming Live with Larry · 4:04 · 524 views

The problem

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 si for 1 <= i <= k is in wordList. Note that beginWord does not need to be in wordList.
  • 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 <= 5
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 500
  • wordList[i].length == beginWord.length
  • beginWord, endWord, and wordList[i] consist of lowercase English letters.
  • beginWord != endWord
  • All the words in wordList are unique.
  • The sum of all shortest transformation sequences does not exceed 105.

Approach overview

Problem Overview: You are given a beginWord, an endWord, and a dictionary of valid words. Each transformation changes exactly one character and must exist in the dictionary. The task is to return all shortest transformation sequences from the start word to the target word.

Approach 1: Classic Breadth-First Search + Backtracking (O(N * L * 26) time, O(N * L) space)

This approach models the dictionary as an implicit graph where each word connects to words that differ by one character. Use Breadth-First Search starting from beginWord to explore transformations level by level. BFS guarantees the first time you reach endWord is the shortest depth. During traversal, store parent relationships in a map so each word keeps track of the words that led to it at the previous level. Instead of stopping immediately when the target appears, finish the entire BFS layer to capture all shortest paths. After BFS builds the parent graph, run a backtracking DFS from endWord to beginWord to reconstruct every valid shortest sequence. The dictionary lookup is optimized using a hash table. The complexity comes from generating up to 26 * L neighbors for each word.

Approach 2: Bidirectional Breadth-First Search (O(N * L * 26) time, O(N * L) space)

Bidirectional BFS improves practical performance by expanding the search simultaneously from beginWord and endWord. Instead of exploring the entire search space from one side, the algorithm always expands the smaller frontier. When the two frontiers meet, the shortest transformation length is determined. While expanding nodes, build adjacency relationships that record how words connect between levels. After the search completes, run a DFS or backtracking step to enumerate all valid sequences using the recorded transitions. This reduces the number of explored states significantly compared with single-direction BFS, especially when the dictionary is large. The time complexity remains proportional to neighbor generation (N * L * 26), but the explored portion of the graph is often much smaller.

Recommended for interviews: The expected solution combines BFS for shortest path discovery and backtracking to reconstruct all valid sequences. Interviewers want to see that you understand why BFS guarantees minimal transformations and how to store parent relationships for path reconstruction. Bidirectional BFS demonstrates deeper optimization skills and is preferred when the dictionary size is large.

Approach 1: Bidirectional Breadth-First Search

The idea is to optimize the BFS by starting the search from both the beginWord and the endWord simultaneously. This bidirectional approach helps in cutting down the search space significantly as the two searches will meet in the middle.

Initially, we create two sets to track words to visit from the beginning side and the ending side. We also use a visited set to ensure we do not revisit any words.

We alternate the search extensions from one side to another to maintain the balance of the search width for the two meet points.

Due to the bidirectional nature, the time complexity can be greatly reduced compared to a conventional BFS.

from collections import defaultdict, deque

def findLadders(beginWord, endWord, wordList):
    def construct_paths(source, destination, tree):
        if source == destination:
            return [[source]]
        return [[source] + path for succ in tree[source] for path in construct_paths(succ, destination, tree)]

    if endWord not in wordList or not endWord or not beginWord or not wordList:
        return []
    tree, words, n = defaultdict(set), set(wordList), len(beginWord)
    found, q, nq = False, {beginWord}, set()
    while q and not found:
        words -= q
        for x in q:
            for y in ([x[:i] + chr(c) + x[i+1:] for c in range(ord('a'), ord('z')+1)] for i in range(n)):
                for z in y:
                    if z in words:
                        if z == endWord:
                            found = True
                        else:
                            nq.add(z)
                        tree[x].add(z)
        q, nq = nq, set()
    return construct_paths(beginWord, endWord, tree)
import java.util.*;

public class WordLadderII {
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        List<List<String>> results = new ArrayList<>();
        Set<String> wordSet = new HashSet<>(wordList);
        if (!wordSet.contains(endWord)) return results;

        Map<String, List<String>> tree = new HashMap<>();
        if (!bfsGraph(tree, beginWord, endWord, wordSet)) return results;

        List<String> temp = new ArrayList<>();
        temp.add(beginWord);
        dfsFindPaths(results, temp, beginWord, endWord, tree);
        return results;
    }

    private boolean bfsGraph(Map<String, List<String>> tree, String beginWord, String endWord, Set<String> wordSet) {
        Queue<String> queue = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        queue.add(beginWord);
        boolean found = false;
        Set<String> tempSet;

        while (!queue.isEmpty() && !found) {
            tempSet = new HashSet<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String word = queue.poll();
                for (String transformed : getTransforms(word)) {
                    if (transformed.equals(endWord)) found = true;

                    if (wordSet.contains(transformed) && visited.add(transformed)) {
                        tempSet.add(transformed);
                        queue.add(transformed);
                        tree.computeIfAbsent(word, k -> new ArrayList<>()).add(transformed);
                    }
                }
            }
            wordSet.removeAll(tempSet);
        }
        return found;
    }

    private List<String> getTransforms(String word) {
        List<String> transformations = new ArrayList<>();
        char[] charArray = word.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            char old = charArray[i];
            for (char c = 'a'; c <= 'z'; c++) {
                charArray[i] = c;
                transformations.add(new String(charArray));
            }
            charArray[i] = old;
        }
        return transformations;
    }

    private void dfsFindPaths(List<List<String>> results, List<String> path, String word, String endWord, Map<String, List<String>> tree) {
        if (word.equals(endWord)) {
            results.add(new ArrayList<>(path));
            return;
        }
        if (!tree.containsKey(word)) return;
        for (String next : tree.get(word)) {
            path.add(next);
            dfsFindPaths(results, path, next, endWord, tree);
            path.remove(path.size() - 1);
        }
    }
}
function findLadders(beginWord, endWord, wordList) {
    const tree = new Map();
    const wordSet = new Set(wordList);
    if (!wordSet.has(endWord)) return [];
    let found = false;
    let q = new Set([beginWord]);
    while (q.size && !found) {
        wordSet.forEach(
            word => q.has(word) ? wordSet.delete(word) : null
        );
        const nextSet = new Set();
        for (let word of q) {
            for (let i = 0; i < word.length; i++) {
                for (let j = 97; j <= 122; j++) {
                    const newWord = word.slice(0, i) + String.fromCharCode(j) + word.slice(i + 1);
                    if (wordSet.has(newWord) || newWord == endWord) {
                        if (!tree.has(word)) tree.set(word, []);
                        tree.get(word).push(newWord);
                        if (newWord == endWord) found = true;
                        nextSet.add(newWord);
                    }
                }
            }
        }
        q = nextSet;
    }

    const res = [];
    function dfs(word, path) {
        if (word === endWord) {
            res.push([...path, word]);
            return;
        }
        if (tree.has(word)) {
            for (let next of tree.get(word)) {
                dfs(next, [...path, word]);
            }
        }
    }
    dfs(beginWord, []);
    return res;
}

This Python code uses a bidirectional approach to construct all possible paths from the beginning to the end word. The set operations ensure efficient word lookups and deletions, while the recursive helper function constructs all paths once the tree is established.

Complexity

Time Complexity: O(N * M * 26) where N is the number of words and M is the word length.
Space Complexity: O(N * M) due to the storage of the graph.

Try this approach in the editor

Approach 2: Classic Breadth-First Search (BFS)

Using a single queue BFS, we explore transformations level by level. We build a graph where each word is a node, and edges are valid single-letter transformations as they appear in the word list. During BFS, paths are stored and checked for minimum length.

We mark words visited in the current level to avoid loops and unnecessary processing.

Once a complete path is found its length is compared to ensure it’s the smallest, and added to results if valid.

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>
using namespace std;

vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
    unordered_set<string> wordSet(wordList.begin(), wordList.end());
    vector<vector<string>> result;
    if (wordSet.find(endWord) == wordSet.end()) return result;

    queue<vector<string>> paths;
    paths.push({beginWord});
    vector<string> visited;

    while (!paths.empty()) {
        int levelSize = paths.size();
        unordered_set<string> currentLevelVisited;

        while (levelSize--) {
            vector<string> path = paths.front(); paths.pop();
            string lastWord = path.back();

            vector<string> newVisited;
            for (int i = 0; i < lastWord.length(); ++i) {
                char original = lastWord[i];
                for (char c = 'a'; c <= 'z'; ++c) {
                    lastWord[i] = c;
                    if (lastWord == endWord) {
                        path.push_back(endWord);
                        result.push_back(path);
                    }
                    if (wordSet.find(lastWord) != wordSet.end()) {
                        vector<string> newPath = path;
                        newPath.push_back(lastWord);
                        paths.push(newPath);
                        currentLevelVisited.insert(lastWord);
                    }
                }
                lastWord[i] = original;
            }
        }
        for (const string& word : currentLevelVisited) {
            wordSet.erase(word);
        }
        if (!result.empty()) break;
    }
    return result;
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef struct Node {
    char *word;
    struct Node *prev;
    struct Node *next;
} Node;

typedef struct Queue {
    Node *front;
    Node *rear;
} Queue;

Queue *initQueue() {
    Queue *q = (Queue *)malloc(sizeof(Queue));
    q->front = q->rear = NULL;
    return q;
}

void enqueue(Queue *q, char *word) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->word = strdup(word);
    newNode->next = NULL;
    if (q->rear) {
        q->rear->next = newNode;
    } else {
        q->front = newNode;
    }
    q->rear = newNode;
}

char *dequeue(Queue *q) {
    if (!q->front) return NULL;
    Node *node = q->front;
    char *word = node->word;
    q->front = q->front->next;
    if (!q->front) {
        q->rear = NULL;
    }
    free(node);
    return word;
}

bool queueIsEmpty(Queue *q) {
    return q->front == NULL;
}

void freeQueue(Queue *q) {
    while (!queueIsEmpty(q)) {
        free(dequeue(q));
    }
    free(q);
}

bool isOneLetterDiff(char *word1, char *word2) {
    int diff = 0;
    for (int i = 0; word1[i]; i++) {
        if (word1[i] != word2[i]) diff++;
        if (diff > 1) return false;
    }
    return diff == 1;
}

char *getTransformedWord(char *word, int index, char c) {
    char *transformedWord = strdup(word);
    transformedWord[index] = c;
    return transformedWord;
}

char **findLadders(char *beginWord, char *endWord, char **wordList, int wordListSize, int *returnSize) {
    Queue *queue = initQueue();
    enqueue(queue, beginWord);

    char visited[wordListSize];
    for (int i = 0; i < wordListSize; i++) visited[i] = 0;
    *returnSize = 0;
    char **result = (char **)malloc(sizeof(char *) * wordListSize);
    while (!queueIsEmpty(queue)) {
        int levelSize = 0;
        Queue *nextLevel = initQueue();
        while (!queueIsEmpty(queue)) {
            char *word = dequeue(queue);
            for (int i = 0; i < wordListSize; i++) {
                if (!visited[i] && isOneLetterDiff(word, wordList[i])) {
                    if (strcmp(wordList[i], endWord) == 0) {
                        result[(*returnSize)++] = strdup(wordList[i]);
                    }
                    enqueue(nextLevel, wordList[i]);
                    visited[i] = 1;
                }
            }
            free(word);
        }
        freeQueue(queue);
        queue = nextLevel;
    }
    return result;
}
using System;
using System.Collections.Generic;

public class Solution {
    public IList<IList<string>> FindLadders(string beginWord, string endWord, IList<string> wordList) {
        var wordSet = new HashSet<string>(wordList);
        var results = new List<IList<string>>();
        if (!wordSet.Contains(endWord)) return results;

        var queue = new Queue<IList<string>>();
        queue.Enqueue(new List<string>(){beginWord});
        var levelVisited = new HashSet<string>();

        while (queue.Count > 0) {
            int levelSize = queue.Count;
            var currentLevelVisited = new HashSet<string>();

            while (levelSize-- > 0) {
                var currentPath = queue.Dequeue();
                var lastWord = currentPath[currentPath.Count - 1];

                if (lastWord.Equals(endWord)) {
                    results.Add(currentPath);
                }

                for (int i = 0; i < lastWord.Length; i++) {
                    char[] currentWordArr = lastWord.ToCharArray();
                    for (char c = 'a'; c <= 'z'; c++) {
                        currentWordArr[i] = c;
                        var newWord = new string(currentWordArr);

                        if (wordSet.Contains(newWord) && !levelVisited.Contains(newWord)) {
                            var newPath = new List<string>(currentPath);
                            newPath.Add(newWord);
                            queue.Enqueue(newPath);
                            currentLevelVisited.Add(newWord);
                        }
                    }
                }
            }
            foreach (var word in currentLevelVisited) {
                wordSet.Remove(word);
                levelVisited.Add(word);
            }
            if (results.Count > 0) break;
        }
        return results;
    }
}

This C++ solution efficiently utilizes a queue to handle Paths aiming to transform beginWord to endWord using BFS, marking each transformation step. It avoids revisiting previously handled transformations by erasing each word once traversed in the BFS for each particular level.

Complexity

Time Complexity: O(N * M * 26).
Space Complexity: O(N * M).

Try this approach in the editor

Approach 3: Default Approach

func findLadders(beginWord string, endWord string, wordList []string) [][]string {
	var ans [][]string
	words := make(map[string]bool)
	for _, word := range wordList {
		words[word] = true
	}
	if !words[endWord] {
		return ans
	}
	words[beginWord] = false
	dist := map[string]int{beginWord: 0}
	prev := map[string]map[string]bool{}
	q := []string{beginWord}
	found := false
	step := 0
	for len(q) > 0 && !found {
		step++
		for i := len(q); i > 0; i-- {
			p := q[0]
			q = q[1:]
			chars := []byte(p)
			for j := 0; j < len(chars); j++ {
				ch := chars[j]
				for k := 'a'; k <= 'z'; k++ {
					chars[j] = byte(k)
					t := string(chars)
					if v, ok := dist[t]; ok {
						if v == step {
							prev[t][p] = true
						}
					}
					if !words[t] {
						continue
					}
					if len(prev[t]) == 0 {
						prev[t] = make(map[string]bool)
					}
					prev[t][p] = true
					words[t] = false
					q = append(q, t)
					dist[t] = step
					if endWord == t {
						found = true
					}
				}
				chars[j] = ch
			}
		}
	}
	var dfs func(path []string, begin, cur string)
	dfs = func(path []string, begin, cur string) {
		if cur == beginWord {
			cp := make([]string, len(path))
			copy(cp, path)
			ans = append(ans, cp)
			return
		}
		for k := range prev[cur] {
			path = append([]string{k}, path...)
			dfs(path, beginWord, k)
			path = path[1:]
		}
	}
	if found {
		path := []string{endWord}
		dfs(path, beginWord, endWord)
	}
	return ans
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Bidirectional Breadth-First Search

Time Complexity: O(N * M * 26) where N is the number of words and M is the word length.
Space Complexity: O(N * M) due to the storage of the graph.

Classic Breadth-First Search (BFS)

Time Complexity: O(N * M * 26).
Space Complexity: O(N * M).

Default Approach–

Complexity analysis

ApproachTimeSpaceWhen to Use
Classic BFS + BacktrackingO(N * L * 26)O(N * L)Standard solution for interviews. Clear logic for finding shortest paths and reconstructing sequences.
Bidirectional BFSO(N * L * 26)O(N * L)Best when the dictionary is large. Expands from both ends to reduce the explored search space.

Frequently asked questions

Is Word Ladder II easy or hard?

Word Ladder II is classified as a Hard problem because it combines multiple concepts: BFS shortest-path search, graph construction from strings, and backtracking to enumerate paths. Handling multiple parents and ensuring only shortest sequences are returned makes the implementation more complex than the basic Word Ladder problem.

How to solve Word Ladder II in O(n)?

A strict O(n) solution is not feasible because each word must generate possible neighbors by modifying characters. The practical optimal approach is BFS with neighbor generation, which runs in about O(N * L * 26). Bidirectional BFS reduces the explored portion of the graph and is the most efficient real-world optimization.

Word Ladder II Python or Java solution?

Python and Java implementations typically use a queue for BFS and a HashMap or dictionary to store parent nodes. After BFS discovers the shortest depth, a recursive backtracking function builds every path from endWord back to beginWord. The same logic works across Python, Java, JavaScript, and C++.

What is the best approach for Word Ladder II?

The most common solution uses Breadth-First Search to discover the shortest transformation depth and then backtracking to reconstruct all paths. BFS guarantees minimal steps because it explores level by level. Parent mappings are stored during traversal so every valid shortest sequence can be rebuilt after the search completes.

Is Word Ladder II asked at Google/Amazon/Meta?

Word Ladder variants are frequently used in interviews at large tech companies including Google, Amazon, and Meta. The problem tests graph modeling, BFS traversal, and path reconstruction techniques. Interviewers often expect a BFS-based shortest path approach combined with backtracking.

What data structure is used in Word Ladder II?

The core data structures include a queue for BFS traversal, a hash set for fast dictionary lookups, and a hash map that stores parent relationships between words. During reconstruction, recursion or a stack is used to backtrack through the parent graph to build each valid sequence.

What is the time complexity of Word Ladder II?

The typical complexity is O(N * L * 26), where N is the number of words in the dictionary and L is the word length. For each word explored by BFS, the algorithm generates up to 26 possible character replacements for each position. Additional DFS backtracking reconstructs paths but does not dominate the overall complexity.

Comments

Now write it yourselfClose this page and solve Word Ladder II in the editor, without looking.
Practise Word Ladder II