找到所需的最小元素数,以使它们的总和等于或超过 S

Posted

技术标签:

【中文标题】找到所需的最小元素数,以使它们的总和等于或超过 S【英文标题】:Find the minimum number of elements required so that their sum equals or exceeds S 【发布时间】:2011-08-23 06:25:21 【问题描述】:

我知道这可以通过对数组进行排序并取较大的数字直到满足所需条件来完成。这至少需要 nlog(n) 排序时间。

nlog(n) 有什么改进吗?

我们可以假设所有数字都是正数。

【问题讨论】:

@equality:是的,我更正了我的帖子。 O(n log(n)) 算法是否不足以让他们相信你是一个好的候选人?对我来说,这听起来是一个不错的解决方案。 @equality: S 不必是恒定的,它只需要知道即可。如果它是恒定的或有界的,您可以通过提前分配存储桶来提高现实世界的性能,但这对算法的理论复杂性没有影响。 @equality:当我说“已知”时,我的意思是“在任何计算之前就知道”。比较排序有严格的n lg(n) 限制的原因是您事先不知道值在什么范围内,有多少等。预先了解其中一些信息是允许非比较的原因排序在某些情况下表现更好。 我认为我的解决方案应该被接受。它为您提供最佳答案,无需任何基于快速整数的排序或对输入做出任何假设。 【参考方案1】:

这是O(n + size(smallest subset) * log(n)) 的算法。如果最小子集远小于数组,则为O(n)

如果我对算法的描述不清楚,请阅读http://en.wikipedia.org/wiki/Heap_%28data_structure%29(虽然细节不多,但细节都在那里)。

    将数组转换为堆,以便最大元素及时可用O(n)。 反复从堆中提取最大的元素,直到它们的总和足够大。这需要O(size(smallest subset) * log(n))

这几乎肯定是他们所希望的答案,尽管没有得到它不应该是破坏交易。

编辑:这是另一种变体,通常更快,但可能更慢。

Walk through elements, until the sum of the first few exceeds S.  Store current_sum.
Copy those elements into an array.
Heapify that array such that the minimum is easy to find, remember the minimum.
For each remaining element in the main array:
    if min(in our heap) < element:
        insert element into heap
        increase current_sum by element
        while S + min(in our heap) < current_sum:
            current_sum -= min(in our heap)
            remove min from heap

如果我们在不操作堆的情况下拒绝大部分数组,这可能比之前的解决方案快两倍。但也有可能变慢,比如当数组中的最后一个元素恰好大于 S 时。

【讨论】:

这是 O(n + K log n) (而不是您声称的 O(n + K log K)),不是吗? +1虽然。这绝对是迄今为止最好的答案。 @Moron:哎呀,你是对的。在此之前我有另一个可能会达到下限,但意识到通常情况会更糟。我稍后会添加该解决方案。 @Moron:添加了第二个解决方案。 +1,这是一个更好的解决方案。我的功夫在堆上很弱;如果您已经知道所有元素,我不知道构建它们可能是线性时间,谢谢!【参考方案2】:

假设数字是整数,您可以改进通常的n lg(n) 排序复杂性,因为在这种情况下,我们有额外的信息,即值介于 0 和 S 之间(出于我们的目的,大于 S 的整数是相同的作为 S)。

由于值的范围是有限的,您可以使用非比较排序算法,例如Pigeonhole Sort 或Radix Sort 低于n lg(n)

请注意,这些方法依赖于 S 的某些函数,因此如果 S 变得足够大(并且 n 保持足够小),您最好恢复为比较排序。

【讨论】:

我可能是错的,但如果你做了类似鸽子洞的事情,你实际上可以只执行排序的第一部分(将数据放入孔中),而不是从末尾拉数据添加直到您点击 >= S,这可以节省您将元素重新排序的步骤。 如果你假设固定大小的整数,你只会知道从 0 到 S 的整数范围。如果你使用 bigints,你会回到n log n @hammar:你什么意思?数据类型的最大值并不重要,只有 S 的值;正如我在回答中指出的那样,就本问题而言,大于 S 的值等同于 S,因为如果数组中有任何值 >= S,则答案为 1。 @veredesmarald:该参数仅在 S 大小固定时才有效。 @hammar:对不起,我不明白你的意思。显然,对于足够大的 S,比较排序的性能更好(正如我最初所说的那样),但是 S 值的编程表示与什么有什么关系?【参考方案3】:

这是该问题的 O(n) 预期时间解决方案。这有点像 Moron 的想法,但我们不会放弃选择算法在每个步骤中所做的工作,而是从可能位于中间的项目开始尝试,而不是使用重复加倍的方法。

或者,它实际上只是quickselect,并为剩余的金额额外记账。

首先,很明显,如果您的元素按排序顺序排列,您可以先选择最大的项目,直到超过所需的总和。我们的解决方案就是这样,除了我们会尽量不去发现排序信息,因为排序很慢。

您希望能够确定给定值是否是截止值。如果我们包含该值和大于它的所有内容,我们会达到或超过 S,但是当我们删除它时,我们会低于 S,那么我们就是金色的。

这是伪代码,我没有针对边缘情况对其进行测试,但这可以理解。

def Solve(arr, s):
  # We could get rid of worse case O(n^2) behavior that basically never happens 
  # by selecting the median here deterministically, but in practice, the constant
  # factor on the algorithm will be much worse.
  p = random_element(arr)
  left_arr, right_arr = partition(arr, p)
  # assume p is in neither left_arr nor right_arr
  right_sum = sum(right_arr)
  if right_sum + p >= s:
    if right_sum < s:
      # solved it, p forms the cut off
      return len(right_arr) + 1    
    # took too much, at least we eliminated left_arr and p
    return Solve(right_arr, s) 
  else:
    # didn't take enough yet, include all elements from and eliminate right_arr and p
    return len(right_arr) + 1 + Solve(left_arr, s - right_sum - p)  

【讨论】:

+1 - 但是当枢轴总是不平衡时,围绕随机枢轴进行分区算法(快速排序等)可能会在最坏的情况下表现不佳。在这种情况下,我不确定这是否意味着 O(n^2) 或 O(n log n)。此外 - 重复求和(天真实现)会破坏性能声明 - 您需要跟踪总和如何随时间变化,因为分区会更改数组以及修改上限/下限以克服该问题。跨度> 是的,最坏的情况是 O(n^2)。您可以通过执行确定性中值查找并围绕它进行旋转来摆脱该因素并将其变成 O(n) 更坏的情况,但这种解决方案在实践中基本上总是会变慢(拥抱随机性!)。我改进了伪代码以不重新计算总和(right_arr),但渐近行为不是必需的,只有常数因子。一旦我们消除了数组的一部分,我们就不需要计算它们的总和,也不需要再次检查它们(当我们丢弃左边时,我们从不取它们,当我们丢弃右边时,我们把它们全部取走)。 好的 - 在重新计算时,我相信你 - 通常我的直觉猜测比我仔细(但从不够仔细)做数学更可靠,但遗憾的是,“更可靠”通常仍然很不可靠。 不错的答案。我更喜欢这个而不是我的。这也可以成为 O(n log(S)) 最坏的情况,而不会损害平均情况。我们有一个有限范围内的整数数组。使每个其他枢轴都位于该范围内整数的中点,而不是数组中的值。这保证在O(log(S)) 步骤中收敛。 (或者,您可以在最后 3 次选择没有足够减少搜索空间的任何时候进行确定性中值查找。这使它成为O(n),并且在平均情况下不会影响性能。)【参考方案4】:

您可以对 Theta(nlogn) 做的一个改进(渐近)是获得 O(n log K) 时间算法,其中 K 是所需的最小元素数。

因此,如果 K 是常数,或者说 log n,这比排序更好(渐近地)。当然,如果 K 是 n^epsilon,那么这并不比 Theta(n logn) 好。

这样做的方法是使用selection algorithms,它可以告诉你O(n)时间内第ith个最大的元素。

现在对 K 进行二分搜索,从 i=1(最大)开始,每回合将 i 加倍等。

你找到第ith个最大的,然后找到第i个最大元素的和并检查它是否大于S。

这样,您将运行 O(log K) 次选择算法(即 O(n)),总运行时间为 O(n log K)。

【讨论】:

我不会想到这一点,因为我知道更快的标准解决方案。但这是一个聪明的答案。 +1 @btilly:仅在理论上 :-) 您的答案在实践中是最好的。我想我知道标准解决方案,但我昏昏欲睡的头脑一定是不知怎么的!【参考方案5】:
    消除数字 鸽子洞排序数字

将元素按排序顺序从高到低求和,直到超过 S。

【讨论】:

请添加复杂度分析。谢谢。

以上是关于找到所需的最小元素数,以使它们的总和等于或超过 S的主要内容,如果未能解决你的问题,请参考以下文章

搜索排序数组中出现次数超过一半的元素所需的最小比较

使序列的所有元素为 0 所需的最小步骤数

给定数组表示 S 所需的最小数字计数

计算最小步骤

应该删除给定字符串S中的最少字符数,以使其成为已排序的字符串[重复]

数组560. 和为K的子数组