问题:
给出两个单词(beginWord和endWord),以及字典的单词列表, 查找从beginWord到的所有最短转换序列 endWord,例如:
一次只能更改一个字母。每个变换的词必须 存在于单词列表中。请注意,beginWord不是转换后的单词。
示例1:
输入:beginWord =“ hit”, endWord =“ cog”, wordList = [“ hot”,“ dot”,“ dog”,“ lot”,“ log”,“ cog”]
输出:[[“ hit”,“ hot”,“ dot”,“ dog”,“ cog”],[“ hit”,“ hot”,“ lot”,“ log”,“ cog”]]
我的解决方案基于此思想,但是如何分析此解决方案的时间和空间复杂性?
1)通过将每个字母转换为26个字母之一来执行从beginWord开始的BFS,并查看转换后的单词是否在wordList中,如果是,则放入队列中。
2)在BFS期间,为所有有效下一个维护一个{word:nextWord}的图形 字词
3)当nextWord到达endWord时,请回溯DFS(预购) 遍历)以获取所有路径。
class Solution:
def findLadders(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
wordListSet = set(wordList+[beginWord])
graph = collections.defaultdict(list)
q = set([beginWord])
count = 0
result = []
while q:
count +=1
newQ = set()
for word in q:
wordListSet.remove(word)
for word in q:
if word == endWord:
self.getAllPaths(graph, beginWord, endWord, result, [])
return result
for i in range(len(word)):
for sub in 'abcdefghijklmnopqrstuvwxyz':
if sub != word[i]:
newWord = word[:i] + sub + word[i+1:]
if newWord in wordListSet:
graph[word].append(newWord)
newQ.add(newWord)
q = newQ
return []
def getAllPaths(self, graph, node, target, result, output):
#This is just a backtracking pre-order traversal DFS on a DAG.
output.append(node)
if node==target:
result.append(output[:])
else:
for child in graph[node]:
self.getAllPaths(graph,child, target, result, output)
output.pop()
我很难解决它的时间和空间复杂性。 我的争论:
时间:O(26*L*N + N
),其中L
是每个单词的平均长度,而N
是wordList中单词的个数< / em>。最糟糕的情况是,每个转换的单词都恰好在列表中,因此每个转换都需要26 * length of word
。 DFS部分仅为O(N)
。因此,渐近的只是O(L*N)
空格:O(N)
答案 0 :(得分:5)
您不会找到所有简单的路径,因为结尾词可能还有其他最短路径。最简单的反例如下:
beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]
您的算法将丢失路径aa -> ba -> bb
。实际上,它最多只能找到一条路径。
您编写的时间复杂度确实为O(L * N)
,但空间复杂度为O(L*N)
,这是图形或wordList
占用的空间。
答案 1 :(得分:1)
答案应该是O(L^2 * n)
在构建一个新词的过程中,总共花费 O(L^2)
。首先我们循环当前词,花费O(L)
;然后为了构建每个新字符串:newWord = word[:i] + sub + word[i+1:]
,这又花费了 O(L)
答案 2 :(得分:0)
这听起来像是一个有趣的问题。是的,答案是O(L * N)
。如果您修复了代码以返回所有解决方案,则递归打印例程为O(L!)
。
您有一个外部循环for all nodes being considered
。这可以等于您的单词表的长度。考虑三个字母组合['aaa', 'aab', ... 'zzz']
的完全连接集合。节点数为26 ^ 3或27576。从aaa
转换为zzz
有六个答案:aaa->zaa->zza->zzz
,aaa->zaa->aza->zzz
,aaa->aza->zza->zzz
等。请考虑所有长度的三个路径(25 + 25 + 25)(25 + 25)(25)或93,750条路径,以确保没有更短的路径。
内部循环有两个选择:for i in range(len(word))
和对get_all_paths()
的递归调用以列出所有路径。您知道内部有一个length_of_word的顺序,表示O(L * N)
。注意O(L * N * 26)
的意思是相同的;大O表示法只关心变化的规模。我还没有证明你在那个get_all_paths循环上保持线性。
这是Dijkstra's Shortest Path的特例。您可以更好地为您的特定问题添加启发式方法。通过一个节点的总路径长度始终大于或等于到目前为止的距离加上仍然错误的字母数。这意味着,在完全连接的情况下,您拥有aaa (0 length)->aab (1)->abb (2)->bbb (3)
,因此可以避免探索aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)
。
您可以更正代码以返回所有单词梯子,而我这样做是here。问题在于,递归getAllPaths()
例程现在的增长速度快于O(L * N)
。在代码示例中,输入具有两组“路径选择”或子图,其中的一组乘以路径数。因此,将节点数增加三倍将使路径选择数增加三倍,从而增加路径选择数。