【问题标题】:Time complexity for two different solutions两种不同解决方案的时间复杂度
【发布时间】:2020-10-21 10:38:20
【问题描述】:

我想了解这两种解决方案在时间复杂度上的差异。 该任务无关紧要,但如果您对 here 感到好奇,请查看说明链接。

这是我的第一个解决方案。正确性得分为 100%,但性能得分为 0%:

def solution(s, p ,q):
    dna = dict({'A': 1, 'C': 2, 'G': 3, 'T': 4})
    result = []
    
    for i in range(len(q)):
        least = 4
        
        for c in set(s[p[i] : q[i] + 1]):
            if least > dna[c]: least = dna[c]
        
        result.append(least)
            
    return result

这是第二种解决方案。正确性和性能得分 100%:

def solution(s, p ,q):
    result = []
    
    for i in range(len(q)):
        if 'A' in s[p[i]:q[i] + 1]: result.append(1)
        elif 'C' in s[p[i]:q[i] + 1]: result.append(2)
        elif 'G' in s[p[i]:q[i] + 1]: result.append(3)
        else: result.append(4)
        
    return list(result)

现在这就是我的看法。在这两种解决方案中,我都遍历了一系列 Q 长度,并且在每次迭代中,我都在对字符串的不同部分进行切片,长度在 1 到 100,000 之间。

这就是我感到困惑的地方,在每次迭代的第一个解决方案中,我将字符串的一部分切片并创建一个集合以删除所有重复项。该集合的长度可以在 1 到 4 之间,因此迭代它必须非常快。我注意到的是,我在每次迭代中只迭代一次。

在每次迭代的第二个解决方案中,我将字符串的一部分切片三遍并遍历它,在最坏的情况下,三遍,长度为 100,000。

那为什么第二种解决方案更快?第一个时间复杂度是O(n*m),第二个是O(n+m)怎么办?

我认为这是因为 infor 运算符,但我在 JavaScript 中使用 indexOf 方法尝试了相同的第二个解决方案,它仍然获得 100% 的性能。但为什么?我可以理解,如果在 Python 中 infor 运算符有不同的实现并且在幕后工作不同,但在 JS 中 indexOf 方法只是应用一个 for 循环。那么它与直接在我的函数中执行 for 循环不一样吗?这不应该是 O(n*m) 时间复杂度吗?

【问题讨论】:

  • 只是一个简短的评论“集合的长度可以在 1 到 4 之间,因此迭代它必须非常快” 当然可以,但是需要的时间呢? 创建集合?
  • 我知道创建它需要时间,但每次迭代只需执行一次。遍历一整段字符串 3 次不是慢很多吗?
  • 在我看来,这两种解决方案都是 O(q * n)。通过将 As、Cs 和 Gs 的索引存储在一个数组中,您可以进行二等分以找到索引 x >= P[i] 并且如果 Q[i] >= x 对应的数组是最小因子,否则继续下一个数组的索引。这将是 O(n + q * logn)。
  • 我理解主要思想,但我不熟悉“执行二等分”.. 我是一个菜鸟,我还在学习 :) 我试图对字符串进行迭代而不是 Q,我用 x >= P[i] 和 x

标签: python algorithm performance


【解决方案1】:

你还没有具体说明如何获得性能评级,但无论如何,第二种算法显然更好,主要是因为它使用in 运算符,它在后台调用了一个用 C 实现的函数,这远远超过比python高效。有关此主题的更多信息here。 另外,我不确定,但我认为python解释器不够聪明,不能只对字符串进行一次切片,然后在第二个算法中重复使用相同的部分。 在第一个算法中创建集合似乎也是一个非常昂贵的操作。

最后,性能评级可能不是基于算法复杂度,而是基于不同测试字符串的执行时间?

【讨论】:

  • 不幸的是,我不知道 Codility 如何评价性能。 First solutionSecond solution。以下是测试结果。它说我的第一个解决方案消除了 6 秒的时间限制,而另一个解决方案则在不到半秒的时间内完成。这是一个非常巨大的差异.. 我从来没有想过创建一个集合的成本如此之高.. 如果这实际上是减慢功能的原因
  • 这可能是多种因素的组合:创建一个集合,总是迭代整个数组,而不是使用 in 的 C 实现。
  • 这确实解决了 Python 的情况。但我仍然对为什么它适用于 JavaScript solution 感到困惑,这与 Python 中的完全相同。除非indexOf 方法还调用了一个我不知道的用C 实现的函数。这不就像创建另一个for 循环一样吗? (对不起,我知道这篇文章实际上是为 Python 而不是 JS 设置的,如果你不想回答,你不必回答)
  • 是的,它可能是额外的 for 循环...您是否尝试过将第一个解决方案也移植到 JS?他们仍然得到不同的结果吗?
  • 我试过this。我基于数组 indexOf 的 polyfill 实现了一个myIndexOf 方法(我找不到字符串 indexOf 方法的 polyfill,所以我认为他们实际上做了同样的事情),它没有用。所以我再次尝试here,使用正则表达式,它成功了。所以这可能是我错的地方。我认为字符串 indexOf 方法执行了一个 for 循环,但现在我相信它在后台使用了正则表达式。这就是搜索速度快得多的原因。
【解决方案2】:

我认为复杂性的差异可以很容易地通过一个示例来展示。

考虑以下输入:

s = 'ACGT' * 1000000
# = 'ACGTACGTACGTACGTACGTACGTACGTACGTACGT...ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT'
p = [0]
q = [3999999]

算法 2 很快检查 'A' 是否在 s[0:4000000] 中(它是第一个字符 - 无需遍历整个字符串即可找到它!)。

另一方面,算法 1 必须遍历整个字符串 s[0:4000000] 以构建集合 {'A','C','G','T'},因为遍历整个字符串是检查是否隐藏第五个不同字符的唯一方法字符串中的某处。

重要提示

我说算法 2 在这个例子中应该很快,因为如果 'A' 位于字符串的开头,则测试 'A' in ... 不需要遍历整个字符串来找到 'A'。但是,请注意'A' in s'A' in s[0:4000000] 之间的复杂性可能存在重要差异。问题是如果复制字符串,创建字符串的一部分可能会花费时间(和内存)。而不是切片,您应该使用s.find('A', 0, 4000000),这保证不会构建副本。有关这方面的更多信息:

【讨论】:

  • 它只是解释了性能上的差异。然而,复杂性没有区别。
  • @maraca:感谢您的认可。确实,两种算法之间的最坏情况复杂度没有显着差异。但是,在许多情况下,复杂性会有所不同,尤其是因为总是最多有 4 个不同的字符。
  • 如果字符串是'T' * 1000000,会发生什么?在这种情况下,第一个解决方案会更快吗?还是 in 运算符的 C 语言实现更快? find 你说得对,我没想到。
  • @speed-o-soundSonic 我猜第二种解决方案几乎总是更快,尽管在'T' * 1000000 的情况下差异应该是微不足道的。唯一确定的方法就是计时!
  • 结果证明,在每次迭代中创建集合实际上会大大减慢整个过程。使用 in 运算符进行搜索确实要快得多,即使与正则表达式相比,它在极端情况下的执行速度平均快 2 倍,快 10 倍。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-13
  • 2021-03-15
  • 2018-04-09
  • 1970-01-01
  • 2023-02-21
  • 1970-01-01
相关资源
最近更新 更多