Leetcode 127:單詞接龍(最詳細的解法!!!)
給定兩個單詞(beginWord 和 endWord)和一個字典,找到從 beginWord 到 endWord 的最短轉換序列的長度。轉換需遵循如下規則:
- 每次轉換隻能改變一個字母。
- 轉換過程中的中間單詞必須是字典中的單詞。
說明:
- 如果不存在這樣的轉換序列,返回 0。
- 所有單詞具有相同的長度。
- 所有單詞只由小寫字母組成。
- 字典中不存在重複的單詞。
- 你可以假設 beginWord 和 endWord 是非空的,且二者不相同。
示例 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
由於是求最短路徑,我們很容易想到通過廣度優先遍歷來解決這個問題。現在我們要解決的問題就變成了如何判斷兩個單詞只有一個字母不同
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
尋找最短路徑,也就是開始從後向前查詢。重複上述操作直到start
和end
都是空,此時我們應該返回0
(因為找不到路徑)。關鍵點在於start
和end
的交換,也就是判斷stack.size() < end.size()
。這一步主要含義就是判斷start
和end
誰的下一步可選範圍更小,我們希望從可選範圍小的那一方開始搜尋。
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:
我將該問題的其他語言版本新增到了我的GitHub Leetcode
如有問題,希望大家指出!!!