【问题标题】:Efficient way to compute sum of k largest numbers in a list?计算列表中k个最大数字之和的有效方法?
【发布时间】:2015-05-12 15:33:43
【问题描述】:

我正在阅读一些练习面试问题,对此我有一个问题。假设一个随机整数列表,每个整数都在 1 和 100 之间,计算 k 个最大整数的总和?讨论空间和时间复杂度以及如果每个整数在 1 和 m 之间且 m 变化,方法是否会改变?

我的第一个想法是对数组进行排序并计算最大 k 数的总和。然后,我想如果我使用二叉树结构,我可以从右下角的树开始看。我不确定我的方法是否会改变数字是 1 到 100 还是 1 到 m?有什么最有效的方法吗?

【问题讨论】:

    标签: arrays algorithm list data-structures


    【解决方案1】:

    最有效的方法可能是使用randomized quickselect 之类的东西。它不会完成排序步骤,而是只执行快速排序的分区步骤。如果您不希望按特定顺序排列 k 个最大整数,这就是我要采用的方式。这需要线性时间,但分析不是很简单。 m 对此影响不大。此外,您可以编写代码,以便在对数组进行分区时计算总和。

    Time: O(n)
    Space: O(1)
    

    替代方案是使用类似counting sort 的东西进行排序,它具有线性时间保证。正如您所说,这些值是固定范围内的整数,它会很好地工作。随着 m 的增加,空间需求会增加,但在存储桶内计算总和非常有效。

    Time: O(m) in the worst case (see comments for the argument)
    Space: O(m)
    

    【讨论】:

    • 如果我使用 BST 执行此操作,我会平均查看 nlog(n) 吗?
    • 是的,我想是的。假设您构建一个平衡的二叉搜索树结构,每次插入大约需要 O(log n),并且在构建之后,您必须对整个子树求和。堆会比上述解决方案更合适,但仍然更复杂。
    • 在计数排序中,时间复杂度不应该是O(n+m)吗?
    • @EyalSchneider 我们查看每个元素(其中 n 个),并将它们放入适当的第 m 个桶中。不过,我看不出 m 如何在时间计算中发挥作用。后来,我们最多读取这些桶中的 k 个,并且 k 严格小于 n。
    • @AnirudhRamanathan:在第二阶段,您从最高的一个开始扫描 m 个存储桶。但是,其中许多可能包含计数为 0,因此在最坏的情况下,您必须扫描所有 m 个存储桶。
    【解决方案2】:

    我会说排序可能是不必要的。如果k 很小,那么您需要做的就是维护一个排序列表,该列表会截断超出kth 最大元素的元素。

    在添加元素最大化的最坏情况下,此步骤中的每个步骤都应为O(k)。但是,平均情况要好得多,在一定数量的元素之后,大多数应该小于列表中的最后一个元素,并且操作将是O(log(k))

    【讨论】:

    • “在一定数量的元素之后,大多数应该比列表中的最后一个元素小” - 我不太明白这是怎么回事。即使在平均情况下,它们也是随机数。另外,你会在 k 长度列表上建议什么排序策略?
    • 你不需要排序。只需运行二进制搜索以查找数组中下一个元素的位置,然后将所有其余内容向右移动,丢弃最后一个并将新元素插入它所属的位置。经过一定次数的迭代后,您大部分时间都不需要移动,因为您的最低元素将高于随机样本中的平均元素。
    • 我明白了。您描述的内容与binary insertion sort 相似。
    • @AnirudhRamanathan 谢谢。我知道我所描述的可能有一个名字,但我不知道。
    【解决方案3】:

    一种方法是使用最大大小为 k 的min-heap (implemented as a binary tree)。查看一个新元素是否属于堆只是 O(1),因为它是一个最小堆,并且最小元素的检索是一个恒定时间操作。沿着 O(n) 列表的每个插入步骤(或非插入......在元素太小而无法插入的情况下)是 O(log k)。最后的树遍历和求和步骤是 O(k)。

    总复杂度:

    O (n log k + k) = O(n log k))

    除非您的计算机上运行多个内核,在这种情况下,并行计算是一种选择,只应在最后进行求和。动态计算增加了额外的计算步骤,而实际上根本没有降低你的时间复杂度(你实际上会有更多的计算要做)。无论如何,您总是需要对 k 个元素求和,那么为什么不避免额外的加法和减法步骤呢?

    【讨论】:

    • 这是否与使用优先级队列来保存 K 个最大数字的列表具有相似的复杂性?
    • @user1529412 在en.wikipedia.org/wiki/Priority_queue 中它说“虽然优先级队列通常用堆实现,但它们在概念上与堆不同。优先级队列是一个抽象概念,如“列表”或“映射” ; 就像列表可以用链表或数组来实现一样,优先队列也可以用堆或各种其他方法(例如无序数组)来实现。”所以是的,它与优先级队列的通用实现具有相同的复杂性。
    • @Shashank 你需要使用最小堆。假设 1,2,3,4,5 是数字,您需要找到 2 个最大数字的总和。当我读取 3 时,我的初始最小堆包含 1,2,我删除 1 并插入 3 现在我的堆变为 2、3。当我读取 4 时,我删除 2 并插入 4。现在我的堆是 3,4。跨度>
    • @Sandeep 嘿,你是对的。使用最小堆,比较步骤只需 O(1) 即可查看下一个元素是否属于堆。基本上,我们将堆的大小保持为 k 最大化,因此使用二叉树实现的平均插入是 O(log k)(最坏情况:O(n))。我理解你的意思,我已经更正了。谢谢你。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-10
    • 2011-03-11
    • 1970-01-01
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多