时间复杂性 - Codility - Ladder - Python

时间:2017-02-04 10:31:16

标签: python algorithm performance

问题是here。我的Python代码是

def solution(A, B):
    if len(A) == 1:
        return [1]

    ways = [0] * (len(A) + 1)

    ways[1], ways[2] = 1, 2
    for i in xrange(3, len(ways)):
        ways[i] = ways[i-1] + ways[i-2]

    result = [1] * len(A)
    for i in xrange(len(A)):
        result[i] = ways[A[i]] & ((1<<B[i]) - 1)

    return result

系统检测到的时间复杂度为O(L ^ 2),我不明白为什么。提前谢谢。

3 个答案:

答案 0 :(得分:2)

首先,让我们看一下运行时真的是O(L ^ 2)。我复制了你的代码的一部分,并用增加的L:

值来运行它
import time
import matplotlib.pyplot as plt

def solution(L):
    if L == 0:
        return
    ways = [0] * (L+5)
    ways[1], ways[2] = 1, 2
    for i in xrange(3, len(ways)):
        ways[i] = ways[i-1] + ways[i-2]

points = []
for L in xrange(0, 100001, 10000):
    start = time.time()
    solution(L)
    points.append(time.time() - start)

plt.plot(points)
plt.show()

结果图是这样的: plot showing O(L^2) growth

要理解为什么这个O(L ^ 2)当明显的时间复杂度&#34;计算表明O(L),注意&#34;时间复杂度&#34;因为它取决于你计算的基本操作,所以它本身并不是一个定义明确的概念。通常,基本操作是理所当然的,但在某些情况下,您需要更加小心。这里,如果将加法计为基本运算,则代码为O(N)。但是,如果计算位(或字节)操作,则代码为O(N ^ 2)。这就是原因:

您正在构建第一个L斐波纳契数的数组。第i个斐波纳契数的长度(以数字表示)是Theta(i)。因此ways[i] = ways[i-1] + ways[i-2]添加两个大约i位的数字,如果计算位或字节操作,则需要O(i)时间。

此观察为您提供此循环的O(L ^ 2)位操作计数:

for i in xrange(3, len(ways)):
    ways[i] = ways[i-1] + ways[i-2]

对于这个程序,计算位操作是非常合理的:当L增加时,你的数字是无限大的,并且大数字的加法在时钟时间而不是O(1)是线性的。

您可以通过计算斐波纳契数mod 2 ^ 32来修复代码的复杂性 - 因为2 ^ 32是2 ^ B [i]的倍数。这将对你正在处理的数字保持有限的限制:

for i in xrange(3, len(ways)):
    ways[i] = (ways[i-1] + ways[i-2]) & ((1<<32) - 1)

代码还有其他一些问题,但这会解决这个问题。

答案 1 :(得分:1)

我已经采用了该功能的相关部分:

def solution(A, B):
    for i in xrange(3, len(A) + 1):  # replaced ways for clarity
        # ...

    for i in xrange(len(A)):
        # ...

    return result

观察:

  1. A是一个可迭代的对象(例如list
  2. 您正在按顺序迭代A的元素
  3. 您的函数的行为取决于 A中元素的数量,使其 O(A)
  4. 您在A上迭代两次,这意味着 2 O(A) - &gt; O(A)
  5. 在第4点,由于 2 是一个常数因子, 2 O(A)仍然在 O(A)

    我认为该页面的测量结果不正确。如果循环已嵌套,然后它将是 O(A²),但循环不是嵌套的。

    这个简短的样本是 O(N²)

    def process_list(my_list):
        for i in range(0, len(my_list)):
            for j in range(0, len(my_list)):
                # do something with my_list[i] and my_list[j]
    

    我没有看到该页面用于“检测”代码的时间复杂度的代码,但我的 guess 是页面正在计算您正在使用的循环数了解代码的大部分实际结构。

    <强> EDIT1:

    请注意,基于this answerlen函数的时间复杂度实际上是 O(1),而不是 O(N),因此页面没有错误地尝试计算其用于时间复杂度的用途。如果它正在这样做,它将错误地声称更大的增长顺序,因为它分别使用 4

    <强> EDIT2:

    正如@PaulHankin指出的那样,渐近分析还取决于什么被认为是“基本操作”。在我的分析中,我使用统一成本法计算了加法和赋值作为“基本操作”,而不是对数成本法,我最初没有提到。

    大多数情况下,简单的算术运算始终被视为基本运算。这是我看到最常做的事情,除非被分析的算法本身就是基本操作(例如乘法函数的时间复杂度),这不是这里的情况。

    我们有不同结果的唯一原因似乎是这种区别。我认为我们都是正确的。

    <强> EDIT3:

    虽然 O(N)中的算法也在 O(N²)中,但我认为说明代码仍在 O中是合理的( N) b / c,在我们使用的抽象层次上,似乎更相关的计算步骤(即更有影响力)在循环中作为输入可迭代的大小的函数{{1 },而不是用于表示每个值的位数。

    考虑以下算法来计算 a n

    A

    在统一成本法下,这是在 O(N)中,因为循环执行 n 次,但在对数成本法下,上述算法结果证明因为在{em> O(N)中的行def function(a, n): r = 1 for i in range(0, n): r *= a return r 的乘法的时间复杂度,所以在 O(N²)中,因为位数为表示每个数字取决于数字本身的大小。

答案 2 :(得分:0)

Codility Ladder竞争最好解决in here

这很棘手。

我们首先为第一个L + 2数字计算斐波那契数列。前两个数字仅用作填充符,因此我们必须将序列索引为A [idx] +1而不是A [idx] -1。第二步是通过删除除n个最低位以外的所有位来替换取模操作