题目概述:有一个没有排序,元素个数为2N的正整数数组。要求把它分割为元素个数为N的两个数组,并使两个子数组的和最接近。
拿到题目后的解题思路就是得到2N个数和的一半,记为half,然后从2N个数中找到N个数,使得N个数相想加的和小于等于但是最接近half。
一开始计算时用了以下的代码:
for(int i=1;i<=n2;i++){
for(int j=ave;j>=arr[i];j--){
dp[j] = getMax(dp[j],dp[j-arr[i]]+arr[i]);
}
}
之后分析了正确的解法。
转解法地址:http://www.cppblog.com/baby-fly/archive/2009/09/24/92392.html
为了便于理解这个算法,先举一个小例子:有3、2、5三个数,求三个数中任意几个数相加小于8的各种结果。
1、首先,这是初始表,候选数是3、2、5,数组d[3][8],横向j代表数相加的和,纵向i代表相加数的个数。
2、开始处理候选数3,蓝色行代表正在处理的行,if(d[i-1][j-3]==Y)d[i][j]=d[i][j]=Y
3、开始处理候选数2,先处理两个数的情况,仍然依照上述公式,得到d[2][5]=Y
5、处理候选数为2,一个数的情况,得到d[1][2]=Y
7、处理候选数为5,两个数的情况,得到d[2][7]=Y 和 d[2][8] = Y
8、处理候选数为5,一个数的情况,得到d[1][5]=Y
9、处理结束。至此我们可以根据该图得出,3、2、5三个数中:(1)任意三个相加小于等于8的情况不存在;(2)任意两个数相加小于等于8的情况存在,最大值为8;(3)任意一个数相加小于等于8的情况存在,最大值为5.
三个结论可以转化成问题:在m个数中,任取n个数相加,使和小于等于但最接近数字k。
根据题目,这个模型就可以描述为:在2N个数中,任取N个数相加,使得和小于等于但是最接近half.
贴出原帖代码,上面示例就是对这个算法的一个弱化的演示
for(i = 0; i < N+1; i++)
for(j = 0; j < sum/2+1; j++)
flag[i][j] = false;
flag[0][0] = true;
for(int k = 1; k <= 2*N; k++) {
for(i = k > N ? N : k; i >= 1; i--) {
//两层外循环是遍历集合S(k,i)
for(j = 0; j <= sum/2; j++) {
if(j >= A[k] && flag[i-1][j-A[k]])
flag[i][j] = true;
}
}
}
for(i = sum/2; i >= 0; i--) {
if(flag[N][i]) {
cout << "minimum delta is " << abs(2*i - sum) << endl;
break;
}
}
至于为什么i是从大到小顺序,这是因为,如果从小到大循环,就会出现一个值重复计算的情况。