【问题标题】:How to analyse space complexity of algorithm for CountNonDivisible?如何分析 CountNonDivisible 算法的空间复杂度?
【发布时间】:2022-01-21 14:35:26
【问题描述】:

我正在尝试分析此algorithm 的最坏情况空间复杂度以解决Codility's CountNonDivisible problem

问题陈述:

给定一个由 N 个整数组成的数组 A。

对于每个满足 0 ≤ i

写一个函数,给定这样一个数组,返回一个序列 表示每个元素的非除数数量的整数。

为以下假设编写一个高效算法:

  • N 是 [1, 50,000] 范围内的整数
  • 数组 A 的每个元素都是 [1, 2N] 范围内的整数。

算法(我添加了cmets):

def solution(A):
    A_max = max(A) # O(1) space
    count = {} # O(1) space

    # Create a count of the occurrences of each element in the input array.
    # O(N) space
    for element in A:
        if element not in count:
            count[element] = 1
        else:
            count[element] += 1

    divisors = {} # O(1) space

    # O(N) space
    for element in A:
        divisors[element] = set([1, element])

    divisor = 2 # O(1) space

    # Space TBC
    while divisor*divisor <= A_max:
        element_candidate = divisor # O(1) space
        while element_candidate <= A_max: # O(1) space
            if element_candidate in divisors and not divisor in divisors[element_candidate]: # O(1) space
                divisors[element_candidate].add(divisor) # O(1) space
                divisors[element_candidate].add(element_candidate//divisor) # O(1) space
            element_candidate += divisor # O(1) space
        divisor += 1 # O(1) space

    result = [0] * len(A) # O(N) space

    # Space TBC
    for idx, element in enumerate(A):
        result[idx] = (len(A) - sum([count.get(divisor,0) for divisor in divisors[element]]))

    return result

article 指出预期的最坏情况空间复杂度为 O(N)。

但是divisors dict 需要空间来存储它存储的除数集。

如果 dict 中的每个值都是整数,我就会清楚为什么最坏情况下的空间复杂度是 O(N)。但是每个值都是一组整数。

所以我认为除数集所需的总空间与除数的总数成正比。

在最坏的情况下,所有这些集合中大约会存储多少个除数?

最坏的情况应该发生在,对于给定的 N,我们最大化存储在所有集合中的除数总数。

为此,我认为我们可以使用以下算法:

  • 构造一个大小为 2N 的数组 B,其元素等于 d(n) sequence 中的前 2N 个值 - 即列出 n 的除数的序列。 (我们取 2N 个值,因为 CountNonDivisible 问题的输入数组中任何元素的最大值为 2N。)令 Bi 为 B 的索引数组。
  • 对 B 和 Bi 的元素进行排序,首先按 B 中的值(按降序),然后按 Bi 中的值(也按降序) )。
  • 然后让最坏情况输入数组 A 为由 Bi 中的前 N ​​个元素组成的子数组。

例如,如果 N = 12,则 2N = 24,并且在排序之前:

Bi = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19、20、21、22、23、24]

B = [1, 2, 2, 3, 2, 4, 2, 4, 3, 4, 2, 6, 2, 4, 4, 5, 2, 6, 2, 6, 4, 4, 2、8]

排序后:

Bi = [24, 20, 18, 12, 16, 22, 21, 15, 14, 10, 8, 6, 9, 4, 23, 19, 17, 13, 11、7、5、3、2、1]

B = [8, 6, 6, 6, 5, 4, 4, 4, 4, 4, 4, 4, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1]

而输入数组 A = [24, 20, 18, 12, 16, 22, 21, 15, 14, 10, 8, 6]

除数总数为 59。

我正在努力解决的是如何对 [1, 50,000] 范围内的任何 N 进行概括。

我假设 Codility 在某处陈述/证明了 O(N) 最坏情况的空间复杂度,但我无法找到在哪里。

我上面的分析是否正确?如果是这样,我将如何完成最坏情况空间复杂度的计算?

如果不是,那么实际上是 O(N) 吗?如果是 O(N),我在分析中做错了什么?

【问题讨论】:

    标签: python algorithm big-o space-complexity


    【解决方案1】:

    解决方案并不是真正的 O(N) 空间,因为它将存储 A 的每个元素的除数列表。由于 1..N 范围内数字的除数总数随 N 增加,因此复杂度将是 O(NxK),其中 K 是 N 上 1..N 的除数的平均数。

    如果你在返回结果之前打印sum(map(len,divisors.values())),你会发现solution([1,2,3,4,5,6,7,9,10])在所有集合中总共有27个条目在除数字典中(1..20 有 66 个条目,1..30 有 111 个条目,1..40 有 158 个条目,依此类推,与 N 的比率从 2.7 增加到 3.95)这表明空间复杂度为 O(Nxf (N)) 其中 f(N) 是随 N 增加的函数。

    简而言之,链接中的算法不满足上述O(N)空间期望。它也不满足 O(NlogN) 时间复杂度的期望。

    如果您要使用 Erathostenes 的筛子(如 ​​Codility 执行语句中所建议的那样),您只需要存储 N 个元素(或更少)的计数器,因为您只需要将多个不同的因子分散到多个实际存在于列表中。这将满足 O(N) 空间要求。

    下面是建议逻辑的一个更简单的实现:

    def solution2(A):
        minA      = max(2,min(A)) # minimum multiple 
        maxA      = max(A)        # maximum multiple
        numCounts = dict.fromkeys(A,0)
        for n in A: numCounts[n] += 1 # distinct counts
        divCounts = numCounts.copy()  # divisor counts
        for n in numCounts:
            for m in range(minA*n,maxA+1,n):     # propagate multiples
                if m in divCounts:
                    divCounts[m] += numCounts[m] # add factor count
        return [len(A)-divCounts[n] for n in A ]
    

    numCounts / divCounts 最多包含 N 个条目(确保 O(N) 空间)。 A 中 > N 的项目在传播循环中根本不会迭代,因此只有

    然而,这将具有大于 O(NlogN) 的时间复杂度,因为传播到倍数的次数可能高达:

    2N/2 + 2N/3 + 2N/4 ... + 2   # e.g. A = [2,3,4...,N,2N]
    

    相当于

    2N*∑(1/i) for [i=2..n]       # this is > N * log(N)
    

    【讨论】:

    • 我试图概括 N 的任何值的最坏情况空间复杂度。另外,我还没有完全理解你的答案。假设 N = 7,这是一个素数,N 没有 N 个因数;它有两个(1 和 7)。请您详细说明为什么最坏的情况是 N 是素数?
    • 我的答案是错误的,我在进一步分析了 Codility 建议的代码后完全重写了它。
    • 您的函数甚至无法解决问题的示例案例[3,1,2,3,6]
    • 你是说谐波级数比对数级增长得快,即,但是it doesn't
    • 我没有看到任何试图证明函数 f 是发散的。所以仍然不清楚空间使用量是否超过 O(N)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-28
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 2020-04-02
    • 1970-01-01
    相关资源
    最近更新 更多