2020/06/07 返回搜索路径的BFS
是一道典型的bfs题目,其实可以用回溯方法,但是复杂度太高。
难点在于如何降低复杂度。
首先是通配符的方法,构建了一个字典,键是通配符,对一个的值是所以符合这个通配符的单词。
其次是结合了bfs和dfs的方法,bfs保证了找到最短的路径,同时在bfs中构建了一个后继字典,这样在dfs中可以方便的得到路径。后继字典的构建保证了不会有绕远的路径出现,因为有visited数组。
from collections import defaultdict
from collections import deque
class Solution:
def findLadders(self, beginWord, endWord, wordList):
# 先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
word_set = set(wordList)
res = []
if len(word_set) == 0 or endWord not in word_set:
return res
successors = defaultdict(set)
# 构建通配符
buckets = defaultdict(list)
for word in wordList:
for i in range(len(beginWord)):
match = word[:i] + '_' + word[i+1:]
buckets[match].append(word)
# 第 1 步:使用广度优先遍历得到后继结点列表 successors
# key:字符串,value:广度优先遍历过程中 key 的后继结点列表
found = self.__bfs(beginWord, endWord, word_set, successors,buckets)
if not found:
return res
# 第 2 步:基于后继结点列表 successors ,使用回溯算法得到所有最短路径列表
if beginWord not in successors:
return
path = [beginWord]
self.__dfs(beginWord, endWord, successors, path, res)
return res
def __bfs(self, beginWord, endWord, word_set, successors, buckets):
queue = deque()
queue.append(beginWord)
visited = set()
visited.add(beginWord)
found = False
word_len = len(beginWord)
next_level_visited = set()
while queue:
current_size = len(queue)
for i in range(current_size):
current_word = queue.popleft()
for j in range(word_len):
match = current_word[:j] + '_' + current_word[j+1:]
for next_word in buckets[match]:
if next_word not in visited:
if next_word == endWord:
found = True
next_level_visited.add(next_word)
queue.append(next_word)
successors[current_word].add(next_word)
if found:
break
# 取两集合全部的元素(并集,等价于将 next_level_visited 里的所有元素添加到 visited 里)
visited |= next_level_visited
next_level_visited.clear()
return found
def __dfs(self, beginWord, endWord, successors, path, res):
if beginWord == endWord:
res.append(path[:])
return
successor_words = successors[beginWord]
for next_word in successor_words:
path.append(next_word)
self.__dfs(next_word, endWord, successors, path, res)
path.pop()
如果觉着这个不够过瘾,还能更酷炫的双向BFS。维护两个邻居集合,begin_visited
和end_visited
。只要找到重合的,就是是找到了最短的路径。需要注意的是后驱字典的更新,与查找的方向有关。
class Solution(object):
def findLadders(self, beginWord, endWord, wordList):
# 先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
word_set = set(wordList)
res = []
if len(word_set) == 0 or endWord not in word_set:
return res
dic = collections.defaultdict(set)
for word in wordList:
for i in range(len(beginWord)):
match = word[:i]+'.'+word[i+1:]
dic[match].add(word)
successors = defaultdict(set)
# 第 1 步:使用广度优先遍历得到后继结点列表 successors
# key:字符串,value:广度优先遍历过程中 key 的后继结点列表
found = self.__bidirectional_bfs(beginWord, endWord, word_set, successors, dic)
if not found:
return res
# 第 2 步:基于后继结点列表 successors ,使用回溯算法得到所有最短路径列表
path = [beginWord]
self.__dfs(beginWord, endWord, successors, path, res)
return res
def __bidirectional_bfs(self, beginWord, endWord, word_set, successors, dic):
visited = set()
visited.add(beginWord)
visited.add(endWord)
begin_visited = set()
begin_visited.add(beginWord)
end_visited = set()
end_visited.add(endWord)
found = False
forward = True
word_len = len(beginWord)
while begin_visited:
# 从小的开始进行,begin_visited和end_visited都是需要寻找的邻居的节点集合
if len(begin_visited) > len(end_visited):
begin_visited, end_visited = end_visited, begin_visited
forward = not forward
next_level_visited = set()
for current_word in begin_visited:
for j in range(word_len):
match = current_word[:j]+'.'+current_word[j+1:]
for next_word in dic[match]:
if next_word in end_visited:
found = True
# 在另一侧找到单词以后,还需把这一层关系添加到「后继结点列表」
self.__add_to_successors(successors, forward, current_word, next_word)
if next_word not in visited:
next_level_visited.add(next_word)
self.__add_to_successors(successors, forward, current_word, next_word)
## !!!这里是关键,维护了begin,是新的需要寻找邻居的节点
begin_visited = next_level_visited
# 取两集合全部的元素(并集,等价于将 next_level_visited 里的所有元素添加到 visited 里)
visited |= next_level_visited
if found:
break
return found
def __add_to_successors(self, successors, forward, current_word, next_word):
if forward:
successors[current_word].add(next_word)
else:
successors[next_word].add(current_word)
def __dfs(self, beginWord, endWord, successors, path, res):
if beginWord == endWord:
res.append(path[:])
return
if beginWord not in successors:
return
successor_words = successors[beginWord]
for next_word in successor_words:
path.append(next_word)
self.__dfs(next_word, endWord, successors, path, res)
path.pop()