【问题标题】:find subset with elements smaller than S找到元素小于 S 的子集
【发布时间】:2024-01-13 23:12:01
【问题描述】:

我必须为以下问题找到一个算法:

输入是自然数的两个数 S 和 k 以及一组未排序的 n 对不同的数。

在 O(n) 中决定是否有 k 个数字的子集总和为

algorithm({x_1, ..., x_n}, k, S):
    if exists |{x_i, ..., x_j}| = k and x_i + ... x_j <= S return true

我没有找到时间复杂度为 O(n) 的解决方案。

我得到的结果是 O(kn),因为我们搜索 k 次最小值并且总和是:

algorithm(a={x_1, ..., x_n}, k, S):
    sum = 0
    for i=1,...,k:
        min = a.popFirst()
        for i=2,...,len(a):
            if(a[i] < min):
                t = a[i]
                a[i] = min
                min = t
        sum += min
    if sum <= S:
        return true
    else:
        return false

这是在 O(n) 中并返回正确的结果。我怎样才能松开k?

谢谢你的帮助,我真的在这方面苦苦挣扎!

【问题讨论】:

  • 子集在原始集中应该是连续的吗?即i &lt;= j in | { x_i,....x_j } |?
  • 没有。它只是集合的 k 个“指数”!意思是 x_j 必须是 x_{i + k}

标签: algorithm set time-complexity subset-sum


【解决方案1】:

快速选择可用于查找 k 个最小的元素:https://en.wikipedia.org/wiki/Quickselect

它基本上是快速排序,只是你只在枢轴的有趣一侧递归。

一个简单的实现在 O(N) 预期 时间内运行,但是使用中位数的中位数来选择一个枢轴,你可以把它变成一个真正的最坏情况绑定:https://en.wikipedia.org/wiki/Median_of_medians

【讨论】:

  • 但是我也需要找到 k 最小的元素,不是吗?这意味着我也有 O(kn) !我仍然需要得到这些 k 元素来总结它们并检查总和是否为
  • @gxor 即使我们只识别了kth 最小的元素,我们也可以在 O(n) 中过滤数组。
  • @gxor 虽然它经常作为查找第 k 个最小元素的算法出现,但 quickselect 实际上确实直接找到了 k 个最小元素——完成后它们将是数组中的前 k 个元素.
【解决方案2】:

你可以从集合中构建一个大小为k 的最小堆。构建它的时间复杂度是O(n) 预期时间和O(n log k) 最坏情况。 堆应该包含集合中的第一个 k 最小元素。

那么直接看到堆中元素的总和就是&lt;= S。您不需要从堆中删除元素来计算总和。只需遍历堆即可计算总和。删除所有元素需要k log k 复杂性。

你甚至不需要考虑下一个更高的元素,因为添加它们会导致总和大于S

【讨论】:

  • 你使用什么 O(n) 算法来构建堆?重复插入是最坏情况 O(n log k)。 (如果输入是随机排序的,则预期时间为 O(n)。但不能保证。)您可以使用 first quickselect 对元素进行分区,但如果这样做,堆将无关紧要。
  • 没有具体的算法。使用通常的 heapify,即筛选堆上的元素,构建堆的严格界限是 O(n),因为并非所有插入都是 O( log n )。直观的解释见this漂亮的答案。
  • 解释了如何构建n 元素堆,而不是如何构建n 中最小的k 元素堆。这些是不同的问题。如果您已经知道需要哪些 k 元素,则可以在 O(k) 中进行堆放,但在这种情况下,您已经有了答案。如果你不知道,标准的 heapify 算法对你没有帮助。
  • @rici 如果你在大小为 k 的堆中插入所有 n 个元素,堆中会包含什么?它包含 k 个最小的元素。
  • 没错,但这不是 heapify 所做的。如果将n 元素一次添加到k 大小的堆中,则成本是最坏情况O(n log k)。链接中的 heapify 算法将 n 元素重新排列到保证 O(n) 的堆中,但它需要所有可用的元素才能做到这一点。它没有办法产生更小的堆。
最近更新 更多