1. 程式人生 > >Leetcode 127:單詞接龍(最詳細的解法!!!)

Leetcode 127:單詞接龍(最詳細的解法!!!)

給定兩個單詞(beginWordendWord)和一個字典,找到從 beginWordendWord 的最短轉換序列的長度。轉換需遵循如下規則:

  1. 每次轉換隻能改變一個字母。
  2. 轉換過程中的中間單詞必須是字典中的單詞。

說明:

  • 如果不存在這樣的轉換序列,返回 0。
  • 所有單詞具有相同的長度。
  • 所有單詞只由小寫字母組成。
  • 字典中不存在重複的單詞。
  • 你可以假設 beginWordendWord 是非空的,且二者不相同。

示例 1:

輸入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
輸出: 5
解釋: 一個最短轉換序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的長度 5。

示例 2:

輸入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
輸出: 0
解釋: endWord "cog" 不在字典中,所以無法進行轉換。

解題思路

很明顯這是一個圖的問題。對於示例1來說

hit - hot - dot
       |  /  |
      lot   dog
       |  /  |
      log - cog

由於是求最短路徑,我們很容易想到通過廣度優先遍歷來解決這個問題。現在我們要解決的問題就變成了如何判斷兩個單詞只有一個字母不同

。最簡的辦法就是通過26個字母替換

class Solution:
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        wordDict = set(wordList)
        if endWord not in wordDict:
            return
0 if beginWord in wordDict: wordDict.remove(beginWord) stack, visited = [(beginWord, 1)], set() while stack: word, step = stack.pop(0) if word not in visited: visited.add(word) if word == endWord: return step for i in range(len(word)): for j in 'abcdefghijklmnopqrstuvwxyz': tmp = word[:i] + j + word[i+1:] if tmp in wordDict and tmp not in visited: stack.append((tmp, step + 1)) return 0

但是這顯然不是最好的做法,我們這裡並不是要將單詞中的每個字母用26個字母替換一遍,而是隻要用特殊字元替換即可。例如hot

_ot  h_t  ho_

只會出現上述三種情況。所以我們對給定的輸入hit也做相同的替換

_it  h_t  hi_

我們看hit的替換是不是出現在dict中,如果是的話,說明hit->hot是可行的,我們要判斷這個路徑之前有沒有訪問過,如果沒訪問過的話,我們將h_t加入stack,同時我們要更新我們的step

class Solution:
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        if endWord not in wordList:
            return 0
        if beginWord in wordList:
            wordList.remove(beginWord)
        wordDict = dict()
        for word in wordList:
            for i in range(len(word)):
                tmp = word[:i] + "_" + word[i+1:]
                wordDict[tmp] = wordDict.get(tmp, []) + [word]
        
        stack, visited = [(beginWord, 1)], set()
        while stack:
            word, step = stack.pop(0)
            if word not in visited:
                visited.add(word)
                if word == endWord:
                    return step
                for i in range(len(word)):
                    tmp = word[:i] + "_" + word[i+1:]
                    neigh_words = wordDict.get(tmp, [])
                    for neigh in neigh_words:
                        if neigh not in visited:
                            stack.append((neigh, step + 1))

        return 0 

這裡我們也可以使用雙向廣度優先搜尋。關於雙向廣度優先搜尋其實非常簡單,我們傳統的廣度優先搜尋是從start->end,而雙向的是start-><-end。我們首先建立一個stack用來儲存每次訪問的元素,然後先從start開始

start: hit
end: cog
wordDict: hot dot dog lot log 
stack:

我們首先將start中的每個元素從wordDict中移除。然後將start中的每個元素的每個位置替換為26個字母,然後判斷替換後的單詞tmp是不是在wordDict中,如果不在就繼續替換,如果在的話,我們就判斷tmp在不在end中,如果在的話,我們返回step+1即可,如果不在,我們將tmp加入到stack中。

start: hit
end: cog
wordDict: hot dot dog lot log 
stack: hot

然後我們判斷stack.size() < end.size(),如果是的話,我們將start替換為stack,不是的話,我們將start替換為end並且將end替換為stack。同時我們要將step+1

start: cog
end: hot
wordDict: hot dot dog lot log 
stack: hot

現在我們就相當於cog->hot尋找最短路徑,也就是開始從後向前查詢。重複上述操作直到startend都是空,此時我們應該返回0(因為找不到路徑)。關鍵點在於startend的交換,也就是判斷stack.size() < end.size()。這一步主要含義就是判斷startend誰的下一步可選範圍更小,我們希望從可選範圍小的那一方開始搜尋。

class Solution:
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        step = 1
        wordDict = set(wordList)
        if endWord not in wordDict:
            return 0

        s1, s2 = set([beginWord]), set([endWord])
        while s1:
            stack = set([])
            for s in s1:
                if s in wordDict:
                    wordDict.remove(s)

            for s in s1:
                for i in range(len(beginWord)):
                    for j in 'abcdefghijklmnopqrstuvwxyz':
                        tmp = s[:i] + j + s[i+1:]
                        if tmp not in wordDict:
                            continue
                        if tmp in s2:
                            step += 1
                            return step
                        stack.add(tmp)

            if len(stack) < len(s2):
                s1 = stack
            else:
                s1, s2 = s2, stack

            step += 1

        return 0 

reference:

https://leetcode.com/problems/word-ladder/discuss/40723/Simple-to-understand-Python-solution-using-list-preprocessing-and-BFS-beats-95

我將該問題的其他語言版本新增到了我的GitHub Leetcode

如有問題,希望大家指出!!!