题目:给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
·如果不存在这样的转换序列,返回一个空列表。
·所有单词具有相同的长度。
·所有单词只由小写字母组成。
·字典中不存在重复的单词。
·你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
思路:这一题和127题的区别在于要找出所有最短序列,且要输出路径。对于所有最短路径好解决,只要找到第一次最短路径时,遍历完当前层即可结束,但难点在于如何处理单词的重复访问问题,即:要保证每层节点的下层节点不会出现该层及之前层出现过的节点;
难点在于要保存路径。
刚开始困在了保存路径的方法上,思考过DFS,但估计对于恒大的Wordlist会超时;也思考过利用单独的列表保存每一条可能的路径,但由于事先不知道最短路径的长度,所以要记录每条路径,但是列表方法难以将单词和路径对应起来。
上一题中BFS使用队列进行层次遍历,队列中只保存了当前的Word,如果在队列中保存当前的路径,路径保存的问题就迎刃而解了。
同时要保证每层节点的下层节点不会出现该层及之前层出现过的节点,就要添加used标志每个单词是否访问过。
本解法中在每次访问队列中,将最后一个单词置位访问状态。(这里是有问题的!!!)
from collections import deque,defaultdict
class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
if endWord not in wordList or not beginWord or not endWord or not wordList:return []
len_word = len(beginWord)
all_combo = defaultdict(list)
used = defaultdict(int)
for word in wordList:
for i in range(len_word):
mid = word[:i] + '*' + word[i+1:]
all_combo[mid].append(word)
used[word] = 0
temp = deque([[beginWord]])
ans = []
shortest = len(wordList) + 2
while temp:
cur_path = temp.popleft()
if len(cur_path) >= shortest:
break
cur_word = cur_path[-1]
used[cur_word] = 1
for i in range(len_word):
mid = cur_word[:i] + '*' +cur_word[i+1:]
for word in all_combo[mid]:
if word != cur_word and not used[word]:
new = cur_path + [word]
if word == endWord:
ans.append(new)
shortest = len(new)
else:
temp.append(new)
return ans
执行结果:通过显示详情
执行用时 :1428 ms, 在所有 Python3 提交中击败了5.28%的用户
内存消耗 :20.9 MB, 在所有 Python3 提交中击败了6.98%的用户
仔细分析上述解法,有个小问题在于单词状态:
···每次要保证当前层的下层节点,不会与该层及之前层中出现过的节点重复,同时,下层节点之间是可能出现相同单词的。
···上述解法只能部分满足“不会与该层及之前层中出现过的节点重复”,因为状态变化是按顺序从前往后,前面节点的下层节点可能会出现该层中后面的节点。
解决办法:状态分层变化
用集合used = set()存储访问过的元素;
用子集合subvisited = set()存储当前层的元素,因为同一层中的单词是可以重复的。
每层遍历完毕后,更新used.update(subvisited)即可。
from collections import deque,defaultdict
class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
if endWord not in wordList or not beginWord or not endWord or not wordList:return []
len_word = len(beginWord)
all_combo = defaultdict(list)
used = set(beginWord)
for word in wordList:
for i in range(len_word):
mid = word[:i] + '*' + word[i+1:]
all_combo[mid].append(word)
temp = deque([[beginWord]])
ans = []
shortest = len(wordList) + 2
while temp:
size = len(temp)
if len(temp[0]) >= shortest:
break
subvisited = set()
for k in range(size):
cur_path = temp.popleft()
cur_word = cur_path[-1]
for i in range(len_word):
mid = cur_word[:i] + '*' +cur_word[i+1:]
for word in all_combo[mid]:
if word not in used:
new = cur_path + [word]
if word == endWord:
ans.append(new)
shortest = len(new)
else:
temp.append(new)
subvisited.add(word)
used.update(subvisited)
return ans
执行结果:通过显示详情
执行用时 :328 ms, 在所有 Python3 提交中击败了52.22%的用户
内存消耗 :17.6 MB, 在所有 Python3 提交中击败了9.30%的用户
另外,使用集合查询的时间复杂度是O(1),列表的时间复杂度是O(n)。
解法二:双向BFS搜索
也是比较经典的一种算法,用于解决一直起点、终点,求图的最短路径。
参考这里