目标
我想编写一个算法(在C
中),该算法返回TRUE
或FALSE
(1
或0
),具体取决于数组{{ 1}}输入中给出的“和和/或子”可以A
(见下面的说明)。请注意,x
的所有值都是在[1,x-1]之间的随机(均匀)采样的整数。
澄清和示例
“sum和/或sub”是指将“+”和“ - ”放在数组的每个元素前面并求和。我们称这个函数为A
。
SumSub
应该返回int SumSub (int* A,int x)
{
...
}
SumSub({2,7,5},10)
为7-2 + 5 = 10。您会注意到TRUE
的第一个元素也可以视为否定,因此A
中元素的顺序无关紧要。
A
应该返回SumSub({2,7,5,2},10)
,因为无法“汇总和/或分配”FALSE
的元素以达到array
的值。请注意,这意味着必须使用x
的所有元素。
复杂性
让A
为n
的长度。如果必须探索所有可能的加号和减号组合,问题的复杂性为O(2 ^ n)。但是,某些组合比其他组合更有可能,因此值得首先探讨(希望输出为A
)。通常,需要从最大数字中减去所有元素的组合是不可能的(因为TRUE
的所有元素都低于A
)。另外,如果x
,尝试添加n>x
的所有元素是没有意义的。
问题
我该如何写这个函数?
答案 0 :(得分:2)
不幸的是,您的问题可以缩短为subset-sum problem,即NP-Complete。因此,无法避免指数解。
答案 1 :(得分:1)
正如你所说,原始问题的解决方案确实是指数级的。但是对于A []中的数字,给定范围[1,x-1],您可以使解多项式。有一个非常简单的动态编程解决方案。
订单:
时间复杂度:O(n ^ 2 * x)
记忆复杂度:O(n ^ 2 * x)
其中,n = A []
中的元素数量您需要为此
使用动态编程方法您知道可以在[-n x,n x]范围内进行的最小,最大范围。创建一个大小为(n)X(2 * n * x + 1)的二维数组。让我们称之为dp [] []
dp [i] [j] =从[0..i-1]获取A []的所有元素是否可以使值j
所以
dp [10] [3] = 1意味着取A []的前10个元素我们可以创建值3
dp [10] [3] = 0表示取A []的前10个元素我们不能创建值3
这是一种伪代码:
int SumSub (int* A,int x)
{
bool dp[][];//set all values of this array 0
dp[0][0] = true;
for(i=1;i<=n;i++) {
int val = A[i-1];
for(j=-n*x;j<=n*x;j++) {
dp[i][j]=dp[ i-1 ][ j + val ] | dp[ i-1 ][ j - val ];
}
}
return dp[n][x];
}
答案 2 :(得分:0)
不幸的是,即使x被限制为0,这也是NP完全的,所以不要期望多项式时间算法。为了说明这一点,我将简单地从NP-hard Partition Problem中进行简化,它会询问给定的多正整数是否可以被分成具有相等和的两个部分:
假设我们有一个由n个正整数B_1,...,B_n组成的分区问题实例。从此创建一个问题实例,其中A_i = B_i,每个1&lt; = i&lt; = n,并设置x = 0。
显然,如果将B分区分为两个部分C和D具有相等的总和,那么还有一个问题实例的解决方案:在C中的每个数字前放一个+
,在D中的每个数字前面-
(或反过来)。由于C和D具有相等的和,因此该表达式必须等于0。
OTOH,如果我们刚刚创建的问题实例的解决方案是YES(TRUE),那么我们可以轻松地将B的分区创建为具有相等总和的两个部分:将所有正项放在一个部分中(比方说,C)和所有负面的术语(当然没有前面的-
)(比方说,D)。由于我们知道表达式的总值为0,因此必须是C中(正)数的总和等于D中数字的(否定)总和。
因此对任一问题实例的YES表示对另一个问题实例的YES,这反过来暗示对任一问题实例的NO意味着对另一个问题实例的NO - 即两个问题实例具有相同的解决方案。因此,如果有可能在多项式时间内解决您的问题,那么通过构造上述问题实例,使用您的多时间算法求解它并报告,也可以在多项式时间内解决NP难分区问题。它给出的结果。