固定子集大小的求和子集

34
子集和问题指的是:

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

通常情况下,这个问题是NP完全问题。不过我很好奇这个稍微变换一下的问题的复杂度是否已知:

给定一组整数,是否有一个大小为k的子集其总和为零?

例如,如果k=1,你可以进行二分查找并在O(log n)时间内得到答案。如果k = 2,则可以将其降至O(n log n)(例如参见从数组中找到和为给定数字的一对元素)。如果k=3,则可以用O(n^2)解决(例如参见从数组中找到三个元素使其总和最接近给定数字)。

是否已知存在可以根据k来确定此问题复杂度的上限?

作为动机,我在思考这个问题如何将数组划分为两个部分,使得这两个部分的平均值相等?,并试图确定它是否真的是NP完全问题。答案取决于是否存在上述所描述的公式。
如果没有一般解决方案,我会非常感兴趣地知道k=4的最优界限。

1
从技术上讲,对于 k=1,下限将是 O(n)(您不能假设输入已排序)。 - awesomo
@awesomo 如果您愿意的话,可以这样做,但是假设输入已排序并不会对问题产生太大影响。 - PengOne
选择五个求和等于S的数。参见https://dev59.com/cHA65IYBdhLWcg3wsgyA#3687124 - sdcvvc
6个回答

16
对于k=4时,空间复杂度为O(n),时间复杂度为O(n2*log(n))。
排序数组。从最小的两个元素和最大的两个元素开始,在非递减顺序中计算所有较小的两个元素之和(a[i]+a[j])和所有较大的两个元素之和(a[k]+a[l])。如果总和小于零,则增加较小的总和;如果总和大于零,则减少较大的总和;当总和为零(成功)或a[i]+a[j]>a[k]+a[l](失败)时停止。
诀窍是以这样的方式迭代遍历所有索引i和j,使得(a[i]+a[j])永远不会减少。对于k和l,(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整数线性规划可能会有一些改进。

更新

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

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


1
这个答案基于Gina和ElKamina的想法。 - Evgeny Kluev
为什么不对 k>4 使用同样的技巧呢?例如,对于 k=6,增加较低的 a[i]+a[j]+a[k] 并减少较高的 a[l]+a[m]+a[n] 直到相遇? - mitchus
@mitchus,对于k>4,这个技巧是可以实现的,但需要超线性空间,例如,对于k=6,优先队列将包含O(n^2)个元素。正如你可以在其他帖子的评论中看到的那样,OP不希望要求超线性空间的解决方案。 - Evgeny Kluev
我明白了。也许楼主应该把这个加到原帖里 :) - mitchus
时间复杂度应该是类似于O(n!/(k!(n-k)!))这样的吧?顺序不重要,也不一定需要重复。 - J. Linne
显示剩余6条评论

4

在W + X + Y + Z = {w + x + y + z | w在W中,x在X中,y在Y中,z在Z中}中确定0是否存在的问题基本上是相同的,除了没有令人烦恼的退化情况(即,这些问题可以用最少的资源相互还原)。

该问题(因此k = 4的原始问题)具有O(n ^ 2 log n)时间,O(n)空间算法。对于k = 2的O(n log n)时间算法(以确定0是否在A + B中),按排序顺序访问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

非常相似的问题:

是否更容易解决子集和问题的这个变体?

仍然是NP完全问题。

如果不是,那么子集和问题也可以用F(1) | F(2) | ... F(n)表示,其中F是您的函数。 这将具有O(O(F(1)) + O(F(2)) + O(F(n))),它仍将是多项式,而我们知道它是NP完全的,因此这是错误的。

请注意,如果对输入进行了某些限制,您可以实现多项式时间。

还要注意,暴力运行时间可以用二项式系数计算。


4
对于固定的 k 值,问题“是否存在一个和为给定值的 k 元子集”可以在多项式时间内解决。算法很简单:检查所有大小为 k 的子集,它们的数量为 O(n^k)。不确定我是否理解你的意思。 - Patrick87
@Patrick87 或许我错了,但是朴素地检查(N K)个子集不就可以了吗,其中(N K)是二项式系数吗?n^k 对我来说没有意义。 - Pubby
2
是的,大小为k的子集有C(n, k)个,而C(n, k)是O(n^k)。我的意思是,k元组的数量是P(n, k),它大于C(n, k),而从n个元素中重复选择k个的方式数是n^k,它大于P(n, k)。 - Patrick87
@Patrick87 我还不确定我理解了。你能写一个答案吗? - Pubby
1
@Neowizard 这是n的多项式,而n^k是k的函数。我同意n^k不是k的多项式,但这不是我对原始问题的理解;我参与了引起PengOne提出这个问题的问题。如果您看到PengOne对Pubby的评论,您会发现PengOne同意我的解释;既然他在问问题,我会说这使得我的解释是正确的。他的问题是,在固定的k下,是否可以比O(n^k)更好地完成。对于小的、特定的k,答案是肯定的。 - Patrick87
显示剩余2条评论

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)。通过模拟虚拟2D矩阵(如果您考虑存储数组的排序版本,则为O(n)),可以将空间复杂度降低到O(1)。

首先关于2D矩阵:对数字进行排序并创建一个使用成对和的矩阵X。现在,矩阵的所有行和列都是排序的。要在此矩阵中搜索值,请搜索对角线上的数字。如果数字在X[i,i]和X[i+1,i+1]之间,则可以通过两个矩阵X[i:N,0:i]和X[0:i,i:N]来将搜索空间减半。结果搜索算法的复杂度为O(log^2n)(我不是很确定,请有人检查一下)。

现在,不使用实际矩阵,而是使用虚拟矩阵,在需要时计算X[i,j],而不是预先计算它们。

最终时间复杂度:O((nlogn)^2)。

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


抱歉,我应该提到我不想使用超过O(n)的空间(最好是O(1))。 - PengOne
在第二步,我们如何确保它们不共享个体?我的意思是,它们没有共同的元素?我怎样用Java检查这一点? - Hengameh
你的回答非常有用,+1 :) - Hengameh

3
要继续 awesomo 的回答......如果我们可以假设数字已排序,那么对于给定的 k,我们可以比 O(n^k) 更好;只需取大小为 (k-1) 的所有 O(n^(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 和奇数和偶数数字之和的特定行为,定义一个递归算法来解决这个问题。
首先,修改问题,使列表中既有偶数又有奇数(如果全部是偶数,则可以通过除以二来实现;如果全部是奇数,则可以通过减去 1 和 k 的目标和,然后根据需要重复)。
接下来,利用偶数目标和只能通过使用偶数个奇数数字达到,奇数目标和只能使用奇数个奇数数字达到的事实。生成适当的奇数子集,并使用偶数数字、检查的奇数数字子集之和减去总和以及 k 减去奇数数字子集的大小调用递归算法。当 k=1 时,进行二进制搜索。如果 k > n(不确定是否会发生),则返回 false。
如果你有很少的奇数数字,这可能允许你非常快地挑选出必须成为获胜子集的术语,或者丢弃不能成为获胜子集的术语。你可以通过使用减法技巧将具有大量偶数数字的问题转换为具有大量奇数数字的等效问题。因此,最坏情况肯定是当偶数数字和奇数数字的数量非常相似时......这就是我现在的情况。这个问题的无用宽松上限比暴力搜索多几个数量级,但我觉得这可能至少和暴力搜索一样好。欢迎讨论!
编辑2:上述内容的示例,仅供说明。
{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

鉴于没有更一般性的答案,这是在赏金到期时最好的答案,因此声望将归... - PengOne

0

时间复杂度显然是O(n^k)(从n个元素中选择k个子集的数量)。

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


真的,但我给出的三个例子都比这个有更好的界限。我想我更关心界限随着 k 的增长而增长的方式,因此更紧的界限更好。 - PengOne
对于匿名的投票者,请证明我的错误。请注意,大O符号是一个上界,我从未声称我的答案是一个紧密的、大Omega上界。 - awesomo
3
你的回答是正确的,但不太有用!它太琐碎了。 - ElKamina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接