Word Ladder II

TypeGraph, BFS
Difficulty
Hard
Last Reviewed12/28/2024

Problem Description

Given two words, beginWord and endWord, and a dictionary wordList, return all the shortest transformation sequences from beginWord to endWord, such that: 1. Only one letter can be changed at a time. 2. Each transformed word must exist in the wordList. Return an empty list if there is no such transformation sequence.

Example 1

Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]

Output: ["hit","hot","dot","dog","cog"]

Explanation: In this example, we need to transform "hit" to "cog" using the word list provided. The transformation is: "hit" -> "hot" -> "dot" -> "dog" -> "cog".

Example 2

Input: beginWord = "hot", endWord = "dog", wordList = ["hot","dog"]

Output: ["hot","dog"]

Explanation: In this example, we can directly transform "hot" to "dog" as both words are in the word list.

Example 3

Input: beginWord = "lost", endWord = "cost", wordList = ["most","fist","lost","cost","fish"]

Output: ["lost","cost"]

Explanation: In this example, we can transform "lost" to "cost" directly as both words are in the word list.

Solution Overview

Initial Approach

BFS with backtracking to find all possible transformations and then build the paths using DFS.

Step by Step Solution

  1. 1. Use BFS to build a graph of all possible transformations from beginWord to endWord.
  2. 2. Use backtracking to find all possible transformations from beginWord to endWord.
  3. 3. Use DFS to build paths from beginWord to endWord using the graph built in step 1.

Solution Code

1from collections import deque
2from collections import defaultdict
3
4
5# Function to find all shortest transformation sequences from beginWord to endWord
6# using Word Ladder II
7
8def findLadders(beginWord, endWord, wordList):
9    # Check if endWord is not in the wordList
10    if endWord not in wordList:
11        return []
12    
13    # Create a set for faster lookup
14    wordSet = set(wordList)
15    
16    # Initialize variables
17    neighbors = defaultdict(list)
18    level = {beginWord: 0}
19    res = []
20    queue = deque([beginWord])
21    
22    # BFS to build the graph and store the levels
23    while queue:
24        word = queue.popleft()
25        for i in range(len(word)):
26            for c in 'abcdefghijklmnopqrstuvwxyz':
27                newWord = word[:i] + c + word[i+1:]
28                if newWord in wordSet:
29                    if newWord not in level:
30                        level[newWord] = level[word] + 1
31                        queue.append(newWord)
32                    neighbors[newWord].append(word)
33        
34    # Backtrack to build the paths
35    def backtrack(path, word):
36        if word == beginWord:
37            res.append([beginWord] + path)
38            return
39        for prevWord in neighbors[word]:
40            if level[prevWord] + 1 == level[word]:
41                backtrack([word] + path, prevWord)
42    
43    # Start the backtracking
44    backtrack([], endWord)
45    
46    return res

Solution Walkthrough

findLadders('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'])

Step 1

1from collections import deque
2from collections import defaultdict

Import necessary libraries for using deque and defaultdict

State: deque and defaultdict libraries imported

Step 2

1def findLadders(beginWord, endWord, wordList):

Define the function to find all shortest transformation sequences

State: Function definition with input parameters

Step 3

1if endWord not in wordList:
2        return []

Check if the endWord is in the wordList, if not, return empty list

State: Check if endWord is in wordList

Step 4

1wordSet = set(wordList)

Create a set for faster lookup of words in the wordList

State: Convert wordList to a set for faster lookup

Step 5

1neighbors = defaultdict(list)
2    level = {beginWord: 0}
3    res = []
4    queue = deque([beginWord])

Initialize data structures for storing neighbors, levels, result, and queue

State: Initialize defaultdict, level dictionary, result list, and queue

Step 6

1while queue:
2        word = queue.popleft()
3        for i in range(len(word)):
4            for c in 'abcdefghijklmnopqrstuvwxyz':

Perform BFS traversal and generate new words by changing each character

State: Perform BFS traversal and generate new words

Step 7

1newWord = word[:i] + c + word[i+1:]
2                if newWord in wordSet:

Generate new word by replacing character at position i with all alphabets and check if it's in the wordSet

State: Generate new word and check if it's in wordSet

Step 8

1if newWord not in level:
2                        level[newWord] = level[word] + 1
3                        queue.append(newWord)
4                    neighbors[newWord].append(word)

Update levels and neighbors for the new word if it's not visited before

State: Update levels and neighbors for the new word

Step 9

1def backtrack(path, word):
2        if word == beginWord:
3            res.append([beginWord] + path)
4            return

Backtrack to build the paths from endWord to beginWord

State: Backtracking function definition

Step 10

1for prevWord in neighbors[word]:
2            if level[prevWord] + 1 == level[word]:
3                backtrack([word] + path, prevWord)

Explore neighbors and backtrack to find all paths from endWord to beginWord

State: Exploring neighbors and backtracking

Step 11

1backtrack([], endWord)
2    
3    return res

Start backtracking with an empty path and endWord to find all paths

State: Start backtracking to find all paths

Complexity Analysis

Time Complexity: O(M^2 * N)

M is the length of each word and N is the total number of words in the input word list. Building the adjacency list takes O(M * N) time, and each transformation of a word to its neighbors takes O(M) time. In the worst case, we might have to explore all possible transformations, resulting in O(M^2 * N) time complexity.

Space Complexity: O(N*L)

N is the number of words in the word list, and L is the maximum length of a word

Breakdown:

    Algorithmic Patterns

    BFS

    How it's used: Exploring nodes level by level, commonly used for shortest path problems

    Where in code: Main algorithm for finding shortest path in graph problems

    Example: Finding the shortest path in a maze

    Common Mistakes

    Mistake 1

    What: Not handling edge cases properly

    Why: This can lead to incorrect outputs or runtime errors

    How to avoid: Ensure your code accounts for all possible edge cases

    Mistake 2

    What: Using inefficient data structures

    Why: This can result in slow performance

    How to avoid: Choose data structures like dictionaries or sets for efficient lookups

    Interview Tips

    • Understand the problem requirements clearly before starting
    • Consider using BFS for exploring all possible paths
    • Use a data structure like a queue to track the paths