给定一组整数,是否有一个非空子集,其总和为零?
这个问题一般是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
的最佳界限非常感兴趣。
答案 0 :(得分:14)
对于k = 4,空间复杂度O(n),时间复杂度O(n 2 * log(n))
对数组进行排序。从2个最小元素和2个最大元素开始,计算非递减顺序中2个元素lesser
的所有(a[i] + a[j])
总和以及非元素中{2}个元素greater
的所有(a[k] + a[l])
总和 - 增加订单。如果总和小于零,则增加lesser
和,如果总和大于零,则减少greater
,当总和为零(成功)或a[i] + a[j] > a[k] + a[l]
(失败)时停止。 / p>
诀窍是以这种方式遍历所有索引i
和j
,(a[i] + a[j])
永远不会减少。对于k
和l
,(a[k] + a[l])
永远不会增加。优先级队列有助于执行此操作:
key=(a[i] + a[j]), value=(i = 0, j = 1)
置于优先级队列。(sum, i, j)
。sum
。(a[i+1] + a[j]), i+1, j
和(a[i] + a[j+1]), i, j+1
置于优先级队列中。要跟踪已使用的元素,请为每个'i'维护一个最大使用'j'的数组。只使用“j”的值比“i”更好。对于k> 4
如果空间复杂度仅限于O(n),我找不到更好的东西,比使用k-4
值的强力和剩余的4
值的上述算法。时间复杂度O(n (k-2) * log(n))。
对于非常大的k
integer linear programming可能会有所改善。
<强>更新强>
如果n
非常大(与最大整数值的顺序相同),则可以实现O(1)优先级队列,从而将复杂性提高到O(n 2 )和O(n (k-2))。
如果n >= k * INT_MAX
,则可以使用具有O(n)空间复杂度的不同算法。预先计算k/2
值的所有可能总和的位集。并使用它来检查其他k/2
值的总和。时间复杂度为O(n (ceil(k / 2)))。
答案 1 :(得分:4)
在W + X + Y + Z = {w + x + y + z |中确定0是否为0的问题W中的w,X中的x,Y中的y,Z中的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))插入优先级队列。重复删除min元素(wi + x,(i,x))并插入(wi + 1 + x,(i + 1,x))。
答案 2 :(得分:2)
非常相似的问题:
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完全的。
请注意,如果输入上有某些边界,则可以实现多项式时间。
另请注意,可以使用二项式系数计算蛮力运行时间。
答案 3 :(得分:2)
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 ,我:N]。由此产生的搜索算法是O(log ^ 2n)(我不是很确定。可以检查一下吗?)。
现在,不使用真实矩阵,而是使用虚拟矩阵,根据需要计算X [i,j],而不是预先计算它们。
产生的时间复杂度:O((nlogn)^ 2)。
PS:在以下链接中,它表示2D排序矩阵搜索的复杂性是O(n)复杂度。如果确实如此(即O(log ^ 2n)不正确),则最终复杂度为O(n ^ 3)。
答案 4 :(得分:1)
时间复杂度非常简单O(n^k)
(来自k
元素的n
个大小的子集数。)
由于k
是一个给定的常量,一个(可能是相当高阶的)多项式上限将复杂性限制为n
的函数。
答案 5 :(得分:1)
以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,并且奇数和偶数的和在某些方面表现,以定义一个递归算法来解决这个问题。
首先,修改问题,使你在列表中同时包含偶数和奇数(如果全部是偶数,则可以通过除以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