【问题标题】:Sum-subset with a fixed subset size具有固定子集大小的 Sum-subset
【发布时间】:2012-02-13 13:12:37
【问题描述】:

sum-subset problem 声明:

给定一组整数,是否存在总和为零的非空子集?

这个问题通常是 NP 完全的。我很好奇这种轻微变体的复杂性是否已知:

给定一组整数,是否存在大小为k 且总和为零的子集?

例如,如果k = 1,您可以进行二分搜索以在O(log n) 中找到答案。如果是k = 2,那么您可以将其降为O(n log n)(例如,参见Find a pair of elements from an array whose sum equals a given number)。如果是k = 3,那么你可以做O(n^2)(例如见Finding three elements in an array whose sum is closest to a given number)。

是否存在可以作为k 函数的已知界限?

作为动机,我正在考虑这个问题How do you partition an array into 2 parts such that the two parts have equal average? 并试图确定它是否实际上是 NP 完全的。答案就在于有没有上面所说的公式。

除非有一个通用的解决方案,否则我很想知道k=4 的最佳界限。

【问题讨论】:

  • 从技术上讲,k=1 的下限是 O(n)(您不能假设输入已排序)
  • @awesomo 当然,如果您愿意,但假设输入已排序并不会改变太多问题。

标签: algorithm language-agnostic np


【解决方案1】:

对于k=4,空间复杂度O(n),时间复杂度O(n2 * log(n))

对数组进行排序。从 2 个最小和 2 个最大元素开始,按非递减顺序计算所有 lesser 2 个元素的总和 (a[i] + a[j]),并按非递增顺序计算所有 greater 2 个元素的总和 (a[k] + a[l])。如果总和小于零,则增加lesser sum,如果总和大于零,则减少greater 1,当总和为零(成功)或a[i] + a[j] > a[k] + a[l](失败)时停止。

诀窍是遍历所有索引ij,使(a[i] + a[j]) 永远不会减少。对于kl(a[k] + a[l]) 永远不会增加。优先级队列有助于做到这一点:

  1. key=(a[i] + a[j]), value=(i = 0, j = 1) 放入优先队列。
  2. 从优先队列中弹出(sum, i, j)
  3. 在上述算法中使用sum
  4. 仅当(a[i+1] + a[j]), i+1, j(a[i] + a[j+1]), i, j+1 尚未使用这些元素时,才将这些元素放入优先队列。要跟踪使用过的元素,请为每个“i”维护一个最大使用过的“j”数组。仅使用大于“i”的“j”值就足够了。
  5. 从第 2 步继续。

对于 k>4

如果空间复杂度限制为 O(n),我找不到比对 k-4 值使用蛮力和对剩余 4 值使用上述算法更好的方法。时间复杂度 O(n(k-2) * log(n)).

对于非常大的k integer linear programming 可能会有一些改进。

更新

如果n 非常大(与最大整数值的顺序相同),则可以实现 O(1) 优先级队列,将复杂度提高到 O(n2) 和 O (n(k-2)).

如果n >= k * INT_MAX,则可以使用具有 O(n) 空间复杂度的不同算法。为所有可能的k/2 值之和预先计算一个位集。并用它来检查其他k/2 值的总和。时间复杂度为 O(n(ceil(k/2)))。

【讨论】:

  • 此答案基于 Gina 和 ElKamina 的想法。
  • 为什么不对k>4 使用同样的技巧呢?例如。对于k=6,增加较低的a[i]+a[j]+a[k] 并减少较高的a[l]+a[m]+a[n],直到开会?
  • @mitchus,这个技巧对于k>4 是可能的,但它需要超线性空间,例如,对于k=6,优先级队列将包含 O(n^2) 个元素。正如您在其他一些帖子的 cmets 中看到的那样,OP 不想要具有超线性空间要求的解决方案。
  • 我明白了。也许OP应该将其添加到原始帖子中:)
  • 你提到k的蛮力> 4.你能详细说明你指的是什么蛮力方法吗?谢谢
【解决方案2】:

判断W+X+Y+Z={w+x+y+z|中是否为0的问题w in W, x in X, y in Y, z in Z} 基本相同,除了没有烦人的退化情况(即问题可以用最少的资源相互简化)。

这个问题(以及 k = 4 的原始问题)有一个 O(n^2 log n)-时间,O(n)-空间算法。 k = 2 的 O(n log n) 时间算法(确定 A + B 中是否为 0)以排序顺序访问 A,以反向排序顺序访问 B。因此,我们只需要一个 A = W + X 的 O(n) 空间迭代器,它可以对称地重复用于 B = Y + Z。让 W = {w1, ..., wn} 按排序顺序。对于 X 中的所有 x,将键值项 (w1 + x, (1, x)) 插入优先级队列。反复删除最小元素(wi + x, (i, x))并插入(wi+1 + x, (i+1, x))。

【讨论】:

    【解决方案3】:

    O(n^2log(n))中k=4的解

    第 1 步:计算两两和并对列表进行排序。有 n(n-1)/2 个和。所以复杂度是O(n^2log(n))。保留总和的个人的身份。

    第 2 步:为上述列表中的每个元素搜索补集,并确保它们不共享“个体”。有 n^2 次搜索,每个搜索的复杂度为 O(log(n))

    编辑:原始算法的空间复杂度为 O(n^2)。通过模拟一个虚拟二维矩阵,空间复杂度可以降低到 O(1)(O(n),如果你考虑空间来存储数组的排序版本)。

    首先关于二维矩阵:对数字进行排序并使用成对求和创建矩阵 X。现在矩阵是这样一种方式,即所有的行和列都被排序。要在此矩阵中搜索一个值,请搜索对角线上的数字。如果数字在 X[i,i] 和 X[i+1,i+1] 之间,则基本上可以将搜索空间减半为矩阵 X[i:N, 0:i] 和 X[0:i , 在]。结果搜索算法为 O(log^2n)(我不太确定。有人可以检查吗?)。

    现在,不要使用实矩阵,而是使用虚拟矩阵,其中 X[i,j] 是根据需要计算的,而不是预先计算它们。

    结果时间复杂度:O( (nlogn)^2 )。

    PS:在下面的链接中,它说 2D 排序矩阵搜索的复杂度是 O(n) 复杂度。如果这是真的(即 O(log^2n) 不正确),那么最终的复杂度是 O(n^3)。

    【讨论】:

    • 对不起,我应该提到我不想使用超过O(n)的空间(最好是O(1))。
    • 在第 2 步中,我们如何确保他们不共享个人信息?我的意思是他们没有共同的元素?如何在 Java 中检查?
    • 你的回答很有用,+1 :)
    【解决方案4】:

    以 awesomo 的回答为基础......如果我们可以假设数字是排序的,对于给定的 k,我们可以做得比 O(n^k) 更好;只需取所有 O(n^(k-1)) 个大小为 (k-1) 的子集,然后对剩余的数字进行二分搜索,当添加到第一个 (k-1) 时,给出目标。这是 O(n^(k-1) log n)。这意味着复杂性肯定比这要低。

    实际上,如果我们知道 k=3 的复杂度为 O(n^2),我们可以对 k > 3 做得更好:选择所有 (k-3)-子集,其中有 O( n^(k-3)),然后在剩余元素上解决 O(n^2) 中的问题。对于 k >= 3,这是 O(n^(k-1))。

    但是,也许你可以做得更好?我会考虑这个的。

    编辑:我最初打算添加很多建议对这个问题提出不同的看法,但我决定发布一个删节版。我鼓励其他发帖者看看他们是否认为这个想法有任何价值。分析很困难,但它可能已经足够疯狂了。

    我们可以利用我们有一个固定的 k 以及奇数和偶数之和以特定方式表现这一事实来定义一个递归算法来解决这个问题。

    首先,修改问题,使列表中同时包含偶数和奇数(如果全部为偶数,则可以通过除以 2 来完成,或者如果全部为奇数,则从目标总和中减去 1 和 k ,并根据需要重复)。

    接下来,利用仅使用偶数个奇数才能达到偶数目标总和的事实,并且仅使用奇数个奇数才能达到奇数目标总和。生成适当的奇数子集,并使用偶数递归调用算法,总和减去被检查的奇数子集的总和,k 减去奇数子集的大小。当 k = 1 时,进行二分查找。如果曾经 k > n(不确定这是否会发生),则返回 false。

    如果您的奇数很少,这可以让您非常快速地选择必须是获胜子集的一部分的术语,或者丢弃不能的术语。您可以使用减法技巧将具有大量偶数的问题转换为具有大量奇数的等效问题。因此,最坏的情况一定是偶数和奇数的数量非常相似......这就是我现在所处的位置。一个无用的宽松上限比蛮力差很多数量级,但我觉得这可能至少与蛮力一样好。欢迎提出想法!

    EDIT2:以上示例,用于说明。

    {1, 2, 2, 6, 7, 7, 20}, k = 3, sum = 20.
    Subset {}:
     {2, 2, 6, 20}, k = 3, sum = 20
     = {1, 1, 3, 10}, k = 3, sum = 10
     Subset {}:
      {10}, k = 3, sum = 10
      Failure
     Subset {1, 1}:
      {10}, k = 1, sum = 8
      Failure
     Subset {1, 3}:
      {10}, k = 1, sum = 6
      Failure
    Subset {1, 7}:
     {2, 2, 6, 20}, k = 1, sum = 12
     Failure
    Subset {7, 7}:
     {2, 2, 6, 20}, k = 1, sum = 6
     Success
    

    【讨论】:

    • 代替更一般的答案,这是赏金到期时最好的,所以代表去...
    【解决方案5】:

    非常相似的问题:

    Is this variant of the subset sum problem easier to solve?

    它仍然是 NP 完全的。

    如果不是,则子集和也将在 P 中,因为它可以表示为 F(1) | F(2) | ... F(n) 其中 F 是您的函数。这将有 O(O(F(1)) + O(F(2)) + O(F(n))) 仍然是多项式,这是不正确的,因为我们知道它是 NP 完全的。

    请注意,如果您对输入有一定的限制,则可以实现多项式时间。

    另请注意,蛮力运行时可以使用二项式系数计算。

    【讨论】:

    • 对于固定的k,对于任何k,问题“是否存在具有给定和的k-子集”可以在多项式时间内解决。该算法很简单:检查所有大小为 k 的子集,其中有 O(n^k)。不知道我是不是误会你了。
    • @Patrick87 也许我错了,但没有 (N K) 个子集可以天真地检查 (N K) 是二项式系数吗? n^k 对我来说毫无意义。
    • 是的,有 C(n, k) 个大小为 k 的子集,C(n, k) 是 O(n^k)。我的意思是,k元组的个数是P(n, k),大于C(n, k),从n中重复选择k的方法数是n^k,大于P (n, k)。
    • @Patrick87 仍然不确定我是否关注。你能写一个答案吗?
    • @Neowizard 是n中的多项式,n^k是k的函数。我同意 n^k 不是 k 中的多项式,但这不是我认为原始问题的意思;我参与了导致 PengOne 提出这个问题的问题。如果你看到 PengOne 对 Pubby 的评论,你会发现 PengOne 同意我的解释;既然他在问这个问题,我会说这使我的解释是正确的。他的问题是,对于固定 k,您是否可以比 O(n^k) 做得更好。对于小而具体的 k,答案​​是肯定的。
    【解决方案6】:

    时间复杂度是微不足道的O(n^k)n 元素中k 大小的子集的数量)。

    由于k 是一个给定的常数,一个(可能相当高阶的)多项式上限将复杂度作为n 的函数。

    【讨论】:

    • 没错,但我给出的所有三个例子都有比这更好的界限。我想我对k 的边界如何增长更感兴趣,所以更紧密的边界更好。
    • 对于匿名投票者,请证明我错了。请注意,Big-Oh 是一个上限,我从未声称我的答案是严格的 Big-Omega 界限。
    • @awesomo 你的回答是对的,但没用!这是微不足道的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-12
    • 2010-12-23
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多