参考:http://blog.csdn.net/linyunzju/article/details/7729774
问题描述:
有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为两个子数组,子数组的元素个数不限,并使两个子数组之和最接近。
分析与思路:
从题目中可以看出,题目的本质就是从2n个整数中找出n个,使得它们的和尽可能靠近所有整数之和的一半。
解法一:
这个问题存储的是从前k个数中选取任意个数,且其和为s的取法是否存在dp[k][s]。之所以将选出的数之和放在下标中,而不是作为dp[k]的值,是因为那种做法不满足动态规划的前提——最优化原理,假设我们找到最优解有k个数p1p2...pk(选出的这k个数之和是最接近sum/2的),但最优解的前k-1个数p1p2...pk-1之和可能并不是最接近sum/2的,也就是说可能在访问到pk之前有另一组数q1q2....qk-1其和相比p1p2...pk-1之和会更接近sum/2,即最优解的子问题并不是最优的,所以不满足最优化原理。因此我们需要将dp[k]的值作为下标存储起来,将这个最优问题转化为判定问题,用带动态规划的思想的递推法来解。
代码如下:
//数组分割:(2n个数组中分成两半让各组之和最接近)
//使用dp[i][s]来标记能否找到i个数,使他们之和为s
//递推过程为dp[i][s]=dp[i-1][s]或dp[i-1][s-Arr[i]],复杂度O(N^2*Sum)
#define MIN(a,b) ((a)>(b)?(b):(a))
const int MAXLEN = 100;
const int MAXSUM = 1000;
int ArrPartition(int Arr[] , int arrLen )
{
//---判断输入数组是否为偶数大小
if(arrLen & 1)
return -1;
//---对数组求和
int sum = 0;
for(int i=0 ; i<arrLen ; i++)
sum += Arr[i];
//---定义判决数组dp
bool dp[MAXLEN][MAXSUM];
memset(dp,0,sizeof(dp));
dp[0][0] = true;
//---动态规划求解判决数组
for(int i=1 ; i<=arrLen ; i++){//对于第i个数
for(int j=MIN(i,arrLen/2) ;j>=1; j--)//i个数中取j个数,j的范围为[i,arrLen/2],由递推公式可知要自底向上求解
for(int v=1 ; v<=sum/2 ; v++){
if(v>=Arr[i-1] && dp[j-1][v-Arr[i-1]])//Arr下标从0开始,与dp下标从1开始不同。
dp[j][v] = true;
}
}
int s;
for (s=sum/2; s>=1 ; s--)
if(dp[arrLen/2][s])
break;
return sum/2-s;
}