【问题标题】:Time complexity of this algorithm: Word Ladder该算法的时间复杂度:字梯
【发布时间】:2018-10-31 02:18:57
【问题描述】:

问题:

给定两个单词(beginWord 和 endWord)和字典的单词列表, 找到从 beginWord 到的所有最短转换序列 endWord,这样:

一次只能更改一个字母。每个转换后的单词必须 存在于单词列表中。请注意,beginWord 不是转换后的单词。

示例 1:

输入:beginWord = "hit", endWord = "齿轮", 单词表 = ["hot","dot","dog","lot","log","cog"]

输出:[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]

我的解决方案就是基于这个思路,但是如何分析这个解决方案的时间和空间复杂度呢?

1) 从 beginWord 开始执行 BFS,将每个字母转换为 26 个字母之一,并查看转换后的单词是否在 wordList 中,如果是,则放入队列中。

2) 在 BFS 期间,为所有有效的 next 维护一个 {word:nextWord} 的图 话

3) 当一个nextWord到达endWord时,做一个回溯DFS(预购 traversal) 在图上获取所有路径。

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 是每个单词的平均长度NwordList 中的单词数。最坏的情况是每个转换的单词都恰好在列表中,所以每个转换都需要26 * length of word。 DFS 部分只是O(N)。所以渐近地它只是O(L*N)

空格:O(N)

【问题讨论】:

  • 您不确定争论的哪一部分,为什么?
  • 我不确定时间复杂度,它似乎需要大于线性,但我无法说服自己。
  • 当(潜在)出度固定时,图搜索在图的大小上是线性的,如下所示。
  • 我的观察是否正确,字典属性(按字母顺序排序)在一般情况下没有帮助,因为前导字符的变化使订单一文不值?
  • 这不是问题,但是您是否考虑过使用 levenshtein 距离?它将以 O(N * L**2) (根据您的符号)计算它,这比您的解决方案渐进地差。但在许多情况下更好(因为 L

标签: python time-complexity breadth-first-search


【解决方案1】:

您不会找到所有简单的路径,因为可能有到结束词的替代最短路径。最简单的反例如下:

beginWord = aa,
endWord = bb
wordList = [aa, ab, ba, bb]

您的算法会错过路径aa -> ba -> bb。事实上,它总是最多只能找到一条路径。

你写的时间复杂度确实是O(L * N),但空间复杂度是O(L*N),这是你的图或wordList占用的空间。

【讨论】:

  • 我认为它确实获得了所有路径。在路径中的每个单词处,graph[] 包含所有转换的列表,然后由烦人的递归例程处理。从糟糕的打字和文档中很难阅读。
  • @CharlesMerriam 到 self.getAllPaths 被调用时,图还没有构建到包含所有转换到最终单词(以及之前的转换)。所以,这个递归例程不会工作,因为它使用不完整的图。
  • 这个问题的最佳解决方案如下。第一步,我们计算到所有单词的距离,直到到达 endWord。第二步,使用 DFS 从 endWord 开始,我们遍历所有经过单词的路径,距离减 1,直到到达 beginWord。
  • 你是对的。应该有一个flag,然后结束for word in q循环再返回。
  • @CharlesMerriam 不幸的是,这个标志没有帮助,因为我们错过了路径中先前单词的其他最短路径。
【解决方案2】:

答案应该是O(L^2 * n)

在构建一个新词的过程中,总共花费了O(L^2)。首先我们循环当前单词,花费O(L);然后构建每个新字符串:newWord = word[:i] + sub + word[i+1:],这又要花费O(L)

【讨论】:

    【解决方案3】:

    这听起来很有趣。是的,答案是O(L * N)。如果您修复了代码以返回所有解决方案,则递归打印例程为 O(L!)

    1. 你有这个外部循环,for all nodes being considered。这可以等于您的单词表的长度。考虑三个字母组合的全连接集['aaa', 'aab', ... 'zzz']。节点数为 26^3 或 27576。从 aaa 转换为 zzz 有六个答案:aaa->zaa->zza->zzzaaa->zaa->aza->zzzaaa->aza->zza->zzz 等。您将考虑所有长度的三个路径,(25+ 25+25)(25+25)(25) 或 93,750 条路径,以确保没有更短的路径。

    2. 对于内部循环,您有两个选择:for i in range(len(word)) 和递归调用 get_all_paths() 以列出所有路径。你知道你对内部有一个 length_of_word 的顺序,暗示O(L * N)。注意O(L * N * 26) 的意思是一样的;大 O 表示法只关心变化的规模。我还没有证明你在 get_all_paths 循环中保持线性。

    3. 这是Dijkstra's Shortest Path 的特例。您可以更好地为您的特定问题添加启发式方法。通过一个节点的总路径长度总是大于或等于到目前为止的距离加上仍然错误的字母数。这意味着,在完全连接的情况下,您拥有aaa (0 length)->aab (1)->abb (2)->bbb (3),因此您可以避免探索aaa (0 actual + 3 heuristic) -> aab (1 actual + 3 heuristic)

    4. 您可以更正代码以返回所有单词阶梯,我这样做了here。问题是递归getAllPaths() 例程现在比O(L * N) 增长得更快。在代码示例中,输入有两组“路径选择”或子图,其中的一组乘以路径的数量。因此,将节点数量增加三倍将使路径选择的数量增加三倍,从而使路径选择的数量增加三倍。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-25
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-09
      相关资源
      最近更新 更多