126. 单词接龙2

LeetCode 126. Word Ladder II 中文解释 Chinese Version
该题思路为bfs+dfs.

  1. 用bfs标记最短距离, 并记录前驱节点
  2. 用dfs从终点遍历回起点

如BFS+DFS,有几个超时的坑所说, 要作两个优化:

  1. bfs时建立反向邻接表
  2. 获取邻近单词时, 要枚举其可能的邻近单词来收集, 而不能遍历所有单词逐一比对, 因为单词量很大时, 耗时较多。

得出如下代码:

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之间有边, 此时有三种情况:

  1. 如果words[w][0]flaot("inf"), 它一定没被访问过, 有dist+1 < words[w][0], 要标记最短距离, 并加入前驱节点
  2. 否则words[w][0]是最短距离, dist+1不可能比它更小
    1. 如果相等, 说明是在同一层中, 以同样的最短路径访问到w, 也要加入前驱节点. (此时words[w][0] = dist+1next_set.add(w)操作无副作用, 为了代码简单, 与第一条并在一起写了)
    2. 否则, 以非最短距离访问到w(绕远路了).有dist+1 > words[w][0], 不作任何操作.
      合并逻辑1.和2.1, 则当dist+1 <= words[w][0]时, 都要加入前驱节点, 且加入next_set, 下一轮bfs要遍历.

(此处自行脑补三种情况的图)

最后dfs从终点遍历回起点, 遇到起点时加入结果集, 记得加入翻转的路径:

if cur == endWord:
	r_path = path.copy()
	r_path.reverse()
	res.append(r_path)
	path.pop()
	return

总结

本题考验在BFS中记录最短距离和最短路径前驱节点的操作.
然后是利用前驱节点, 从终点DFS回到起点的操作

你可能感兴趣的:(算法)