A transformation sequence from word beginWord
to word endWord
using a dictionary wordList
is a sequence of words beginWord -> s1 -> s2 -> ... -> sk
such that:
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]
.
For example:
Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] Output: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
There are 2 shortest transformation sequences: "hit" -> "hot" -> "dot" -> "dog" -> "cog" "hit" -> "hot" -> "lot" -> "log" -> "cog"
Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] Output: []
Can you provide a Python function that implements this and explain the time and space complexity?
from collections import defaultdict
def findLadders(beginWord: str, endWord: str, wordList: list[str]) -> list[list[str]]:
# Convert wordList to a set for faster lookup
wordList = set(wordList)
if endWord not in wordList:
return []
# Build the adjacency list (graph) for words that differ by one letter
adj = defaultdict(list)
all_words = wordList | {beginWord}
for word1 in all_words:
for word2 in all_words:
if word1 != word2 and differ_by_one_letter(word1, word2):
adj[word1].append(word2)
# BFS to find the shortest paths
queue = [(beginWord, [beginWord])]
visited = {beginWord}
shortest_length = float('inf')
result = []
while queue:
curr_word, path = queue.pop(0)
if len(path) > shortest_length:
break
if curr_word == endWord:
shortest_length = len(path)
result.append(path)
continue
for neighbor in adj[curr_word]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
# Filter result to only include shortest paths
shortest_paths = []
for path in result:
if len(path) == shortest_length:
shortest_paths.append(path)
return shortest_paths
def differ_by_one_letter(word1: str, word2: str) -> bool:
if len(word1) != len(word2):
return False
diff_count = 0
for i in range(len(word1)):
if word1[i] != word2[i]:
diff_count += 1
return diff_count == 1
# Example usage
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log","cog"]
print(findLadders(beginWord, endWord, wordList))
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
print(findLadders(beginWord, endWord, wordList))
The most straightforward approach would be to perform a Breadth-First Search (BFS) on the graph of words. Each word represents a node, and an edge exists between two words if they differ by only one letter. The BFS would start at the beginWord
and explore layer by layer until it finds the endWord
. This approach finds all possible paths but may not be efficient in finding the shortest paths without further optimization.
The optimized approach still uses BFS but incorporates several key improvements:
wordList
, this step takes O(N^2 * L) time.endWord
Not in wordList
: If the endWord
is not in the wordList
, there can be no transformation, so return an empty list.beginWord
to endWord
, the BFS will terminate without finding any path, and an empty list should be returned.beginWord
and endWord
are the Same: If beginWord
and endWord
are identical, and the word list contains this word, return a list containing a list with just the beginWord
(or endWord
). If the word list does not contain the beginWord
, there's still no transformation, return an empty list.