127 Word Ladder

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 the number of words in the shortest transformation sequence from beginWord to endWord, or 0 if no such sequence exists.

Example 1:

Input: beginWord = "hit", endWord = "cog",
       wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: One shortest transformation sequence is
"hit" -> "hot" -> "dot" -> "dog" -> cog", which is 5 words long.

Example 2:

Input: beginWord = "hit", endWord = "cog",
       wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList,
therefore there is no valid transformation sequence.

Constraints:

  • 1 <= beginWord.length <= 10
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 5000
  • wordList[i].length == beginWord.length
  • beginWord, endWord, and wordList[i] consist of lowercase English letters.
  • beginWord != endWord
  • All the words in wordList are unique.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from collections import defaultdict, deque

class Solution:
    def __init__(self):
        self.len = 0
        self.all_d = defaultdict(list)
    
    def get_inter_word(self, word: str, i: int) -> str:
        return word[:i] + "*" + word[i+1:]
    
    def visit_word(self, q: deque, visited: dict, others_visited:dict) -> Optional[int]:
        for _ in range(len(q)):
            current_word = q.popleft()
            for i in range(self.len):
                for word in self.all_d[self.get_inter_word(current_word, i)]:
                    if word in others_visited:
                        return visited[current_word] + others_visited[word]
                    if word not in visited:
                        visited[word] = visited[current_word] + 1
                        q.append(word)
        return None
    
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        if (not beginWord
            or not endWord
            or not wordList
            or endWord not in wordList):
            return 0
        
        self.len = len(beginWord)
        
        for word in wordList:
            for i in range(self.len):
                self.all_d[self.get_inter_word(word, i)].append(word)
            
        q_begin = deque([beginWord])
        q_end = deque([endWord])

        visited_begin = {beginWord: 1}
        visited_end = {endWord: 1}
        ans = None

        while q_begin and q_end:
            if len(q_begin) <= len(q_end):
                ans = self.visit_word(q_begin, visited_begin, visited_end)
            else:
                ans = self.visit_word(q_end, visited_end, visited_begin)
            if ans:
                return ans
        return 0