【问题标题】:How can I reduce the complexity of my dictionary solution to Codility Max Counters如何将字典解决方案的复杂性降低到 Codility Max Counters
【发布时间】:2020-03-08 14:06:23
【问题描述】:

我真的很高兴找到这样一个简单的解决方案,直到我被复杂性所困扰。我可以做些什么来降低复杂性,如果有的话,或者我需要找到不同的方法吗?

问题:

给你 N 个计数器,初始设置为 0,你有两种可能的操作:

increase(X) - 计数器 X 增加 1, max counter - 所有计数器都设置为任何计数器的最大值。 给出了一个由 M 个整数组成的非空数组 A。这个数组代表连续的操作:

if A[K] = X, such that 1 ≤ X ≤ N,那么操作K就是增加(X), if A[K] = N + 1 然后操作 K 是最大计数器。 例如,给定整数 N = 5 和数组 A 使得:

A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4

每次连续操作后计数器的值将是:

(0, 0, 1, 0, 0)
(0, 0, 1, 1, 0)
(0, 0, 1, 2, 0)
(2, 2, 2, 2, 2)
(3, 2, 2, 2, 2)
(3, 2, 2, 3, 2)
(3, 2, 2, 4, 2)

目标是计算所有操作后每个计数器的值。

写一个函数:

def solution(N, A)

给定一个整数 N 和一个由 M 个整数组成的非空数组 A,返回一个表示计数器值的整数序列。

结果数组应作为整数数组返回。

例如,给定:

A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4

该函数应返回 [3, 2, 2, 4, 2],如上所述。

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

N 和 M 是 [1..100,000] 范围内的整数; 数组 A 的每个元素都是 [1..N + 1] 范围内的整数。

我的解决方案复杂度为 O(N*M)

def solution(N,A):

    # Step 1 - initialise
    counter = [0]*N
    counter_label = list(range(1,N+1))

    counter_dict = dict(zip(counter_label,counter))    

    # Step 2a iterate through A
    for i in range(len(A)):
        # Step 2B step through each counter
        for l in counter_label:
            if A[i] > N:
                counter_dict[l] = max(counter_dict.values())
            if A[i] == l:
                counter_dict[l] += 1
    return list(counter_dict.values())

我的函数为示例生成了正确的解决方案,但是对于大量数组会超时:

A = [3,4,4,6,1,4,4]
N = 5
print(solution(N,A))

[3, 2, 2, 4, 2]

【问题讨论】:

  • 你的算法要解决什么问题? “Codility Max Counters”对不熟悉该问题的人没有任何意义,因此请编辑您的问题以解释这一点(不要只是添加链接)。
  • 另外,您的代码只是一个函数定义。请发布minimal reproducible example 示例输入和预期输出
  • 谢谢!更新了您的反馈

标签: python-3.x time-complexity


【解决方案1】:

这种简单的实现是将计数器存储在一个列表中,通过在 O(1) 时间内递增该索引处的计数器来回答“增加”查询,并通过找到最大值并更新来回答“最大”查询O(N) 时间内的每个计数器。这意味着回答 M 个查询需要 O(MN) 时间。对于多达 100,000 个查询和多达 100,000 个计数器,O(MN) 时间还不够好。

您的实现实际上比这差得多,因为您通过在 O(N) 时间内迭代所有计数器来回答“增加”查询,并且通过迭代所有计数器并重新计算来回答“最大”查询每次迭代的最大值,每个“最大”查询花费 O(N2) 时间。这意味着您的整体时间复杂度为 O(MN2)。


要实现线性时间,您需要能够在 O(1) 摊销时间内回答每个查询。这意味着在不遍历每个计数器的情况下回答“最大”查询。一种方法是将计数器存储在字典中,并跟踪当前和以前的最大值。

  • 要回答“增加”查询,请增加字典中的计数器,如果字典中不存在计数器,则使用先前的最大值作为计数器的后备,并在适当的情况下更新当前最大值。
  • 要回答“最大值”查询,请将字典替换为新的空字典,并更新之前的最大值。

这是一个实现:

def solution(N, A):
    counters = dict()
    current_max, prev_max = 0, 0
    for q in A:
        if q == N + 1:
            # max query
            counters = dict()
            prev_max = current_max
        else:
            # increase query
            c = counters.get(q, prev_max) + 1
            counters[q] = c
            current_max = max(current_max, c)
    # return the counters as a list
    return [counters.get(i, prev_max) for i in range(1, N+1)]

替换字典需要 O(1) 分摊时间;尽管当字典的大小为 n 时它为垃圾收集器创建了 O(n) 工作,但这可以通过 Ω(n)“增加”查询进行分摊,这些查询必须发生以使字典变大。因此,回答任何类型的 M 个查询都需要 O(M) 时间。最后,我们需要建立一个计数器值的列表,这需要 O(N) 时间,但这只需要做一次。

因此,整体时间复杂度为 O(M + N),线性时间。这是asymptotically optimal,因为您必须为每个 M 个查询做一些事情,并且您必须输出一个大小为 N 的列表。

【讨论】:

  • 我认为你也可以在 O(1) 中进行最大查询,即非摊销,方法是不重置字典,而是在读取时最大化:repl.it/repls/BurlyGracefulError
  • 哦,还有一个摊销 O(1) 的版本:repl.it/repls/WoodenElderlyDeeplearning
  • @HeapOverflow 是的,尽管任何涉及添加到 dict 或列表的操作都需要 O(1) 摊销,因为有时必须重新分配底层数组。
  • 对。虽然我的第一个建议中的最大查询没有这样做,但至少那部分确实是 O(1)。无论如何,现在发布我的第二个版本和另一个版本作为答案。挺喜欢Codility的,之前没用过,不过他们的评测页面看起来很整洁,显然可以分享。
  • 如果您使用列表而不是字典,您可以在两个查询中获得“真正的”O(1),但这并不是真正的收获,因为初始化列表需要 O(N) 时间.还有一个使用“时间戳”来避免清除字典的解决方案;你在字典中插入一对(val, t),增加变量t而不是清除字典,并且只有在与之配对的“时间戳”等于t的当前值时才使用val
【解决方案2】:

基于@kaya3's answer 的解决方案。他基本上是在自己实现一个 defaultdict,这里我只使用 Python 的。 Got 100% on Codility.

from collections import defaultdict

def solution(N, A):
    counters = defaultdict(int)
    for a in A:
        if a <= N:
            counters[a] += 1
        elif counters:
            counters = defaultdict(max(counters.values()).__int__)
    return [counters[i] for i in range(1, N+1)]

另一个版本,也是got 100% on Codility

from collections import defaultdict

def solution(N, A):
    default = 0
    counters = defaultdict(lambda: default)
    for a in A:
        if a <= N:
            counters[a] += 1
        elif counters:
            default = max(counters.values())
            counters.clear()
    return [counters[i] for i in range(1, N+1)]

【讨论】:

    【解决方案3】:

    我最近完成了这是我的 100% 解决方案。

    def solution(N,A):
    # write your code in Python 3.6
    
    counter = [0]*N
    max_counter = 0
    if len(set(A) - set([N+1])) == 0:
      return counter
    for k in range(len(A)):
        if A[k]==N+1:
                counter = [max_counter]*N
        else:
            if A[k]-1 <=N -1:
                counter[A[k]-1]+=1
                if counter[A[k]-1]>max_counter:
                    max_counter= counter[A[k]-1]
                
    return counter
    

    【讨论】:

      猜你喜欢
      • 2013-12-28
      • 1970-01-01
      • 2013-10-28
      • 2020-12-11
      • 2017-02-27
      • 1970-01-01
      • 1970-01-01
      • 2021-06-29
      • 1970-01-01
      相关资源
      最近更新 更多