LeetCode 126. Word Ladder II 中文解释 Chinese Version
该题思路为bfs+dfs.
- 用bfs标记最短距离, 并记录前驱节点
- 用dfs从终点遍历回起点
如BFS+DFS,有几个超时的坑所说, 要作两个优化:
- bfs时建立反向邻接表
- 获取邻近单词时, 要枚举其可能的邻近单词来收集, 而不能遍历所有单词逐一比对, 因为单词量很大时, 耗时较多。
得出如下代码:
import queue
class Solution:
def dfs(self, cur, path, endWord, words, res):
path.append(cur)
if cur == endWord:
r_path = path.copy()
r_path.reverse()
res.append(r_path)
path.pop()
return
for pred in words[cur][1]:
self.dfs(pred, path, endWord, words, res)
path.pop()
def bfs(self, start_set, words, dist, end_word, found):
if not start_set or found:
return
next_set = set()
for word in start_set:
if word == end_word:
found = True
return
# 枚举每个可能的邻近单词
for i in range(len(word)):
for j in range(ord('a'), ord('z')+1):
w = word[:i] + chr(j) + word[i+1:]
if w in words and dist+1 <= words[w][0]:
words[w][0] = dist+1
next_set.add(w)
words[w][1].append(word)
self.bfs(next_set, words, dist+1, end_word, found)
def findLadders(self, beginWord: str, endWord: str, wordList):
words = dict()
for word in wordList:
words[word] = [float("inf"), []] # dist, preds
words[beginWord] = [0, []]
if endWord not in words:
return []
self.bfs({beginWord}, words, 0, endWord, False)
res = []
self.dfs(endWord, [], beginWord, words, res)
return res
这里有几个注意的点:
words记录的两个变量含义, 分别为从源节点出发的最短距离, 和最短路径的前驱节点.
words = dict()
for word in wordList:
words[word] = [float("inf"), []] # dist, preds
words[beginWord] = [0, []]
words[w][0]
只有两个可能的值, float("inf")
代表该节点还没被访问过, dist代表该节点的最短距离.
通过逐一变换单词中某个字母来枚举可能邻近的单词:
for i in range(len(word)):
for j in range(ord('a'), ord('z')+1):
w = word[:i] + chr(j) + word[i+1:]
判断是否标记最短距离的时候, 合并了两条逻辑.
if w in words and dist+1 <= words[w][0]:
words[w][0] = dist+1
next_set.add(w)
words[w][1].append(word)
如果w in words
, 我们能确定w与word之间有边, 此时有三种情况:
- 如果
words[w][0]
为flaot("inf")
, 它一定没被访问过, 有dist+1 < words[w][0]
, 要标记最短距离, 并加入前驱节点 - 否则
words[w][0]
是最短距离,dist+1
不可能比它更小- 如果相等, 说明是在同一层中, 以同样的最短路径访问到w, 也要加入前驱节点. (此时
words[w][0] = dist+1
和next_set.add(w)
操作无副作用, 为了代码简单, 与第一条并在一起写了) - 否则, 以非最短距离访问到w(绕远路了).有
dist+1 > words[w][0]
, 不作任何操作.
合并逻辑1.和2.1, 则当dist+1 <= words[w][0]
时, 都要加入前驱节点, 且加入next_set
, 下一轮bfs要遍历.
- 如果相等, 说明是在同一层中, 以同样的最短路径访问到w, 也要加入前驱节点. (此时
(此处自行脑补三种情况的图)
最后dfs从终点遍历回起点, 遇到起点时加入结果集, 记得加入翻转的路径:
if cur == endWord:
r_path = path.copy()
r_path.reverse()
res.append(r_path)
path.pop()
return
总结
本题考验在BFS中记录最短距离和最短路径前驱节点的操作.
然后是利用前驱节点, 从终点DFS回到起点的操作