这个问题的解决方案涉及到子集和问题。
如果有办法求和到数组总和的一半,那么我们可以将所有这些数字设置为负数。其余的数字将是正数。因为这些子集中的每一个的总和都是总和的一半,所以它们各自的总和将为 0。
这是c++中的代码:
#include<stdio.h>
int arr[] = {1, 2, 2, 3, 4};
int n = 5; // size of arr
int sum = 0;
// dp array only needs to be [n + 1][total sum + 1] big
bool dp[30][100];
inline void subset_sum(){
for (int i = 0; i <= sum; i++)
dp[0][i] = false;
for (int i = 0; i <= n; i++)
dp[i][0] = true;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
dp[i][j] = dp[i - 1][j];
if (arr[i - 1] <= j)
dp[i][j] |= dp[i - 1][j - arr[i - 1]];
}
}
}
int main(){
for (int i = 0; i < n; i++)
sum += arr[i];
// run subset sum dp using a bottom-up approach
// True = sum is possible, False = not possible
subset_sum();
int max_half;
for (int i = sum / 2; i>=1; i--){
if (dp[n][i]){ // it is possible to sum to i using values in arr
max_half = i;
break;
}
}
// output will be the closest sum of positives
// and negatives to 0
printf("%d\n", 2 * max_half - sum);
return 0;
}
此代码的输出将是集合中正数和负数组合的最接近的可能总和为 0。
2 * max_half - sum 可以从max_half - (sum - max_half) 推导出来,这将是我们最好的和减去其余数字。
以下是不同数字集及其各自输出的一些示例:
设置:{1, 2, 2, 3, 4},输出:0。
设置:{1, 1, 1, 1, 1},输出:-1。
设置:{5, 2, 6, 8, 9, 2},输出:0。
设置:{1, 50},输出:-49。
子集和问题网上有many explanations,这里就不解释了。
这段代码的时间复杂度是O(n * sum),空间复杂度是O(n * sum)。
通过使用一维 dp 数组,也可以牺牲一些时间复杂度来提高空间复杂度。