题目概述:有一个没有排序,元素个数为2N的正整数数组。要求把它分割为元素个数为N的两个数组,并使两个子数组的和最接近。
假设数组A[1..2N]所有元素的和是SUM。模仿动态规划解0-1背包问题的策略,令S(k, i)表示前k个元素中任意i个元素的和的集合。显然:
S(k, 1) = {A[i] | 1<= i <= k}
S(k, k) = {A[1]+A[2]+…+A[k]}
S(k, i) = S(k-1, i) U {A[k] + x | x属于S(k-1, i-1) }
按照这个递推公式来计算,最后找出集合S(2N, N)中与SUM最接近的那个和,这便是答案。这个算法的时间复杂度是O(22N).
假设数组A[1..2N]所有元素的和是SUM。模仿动态规划解0-1背包问题的策略,令S(k, i)表示前k个元素中任意i个元素的和的集合。显然:
S(k, 1) = {A[i] | 1<= i <= k}
S(k, k) = {A[1]+A[2]+…+A[k]}
S(k, i) = S(k-1, i) U {A[k] + x | x属于S(k-1, i-1) }
按照这个递推公式来计算,最后找出集合S(2N, N)中与SUM最接近的那个和,这便是答案。这个算法的时间复杂度是O(22N).
因为这个过程中只关注和不大于SUM/2的那个子数组的和。所以集合中重复的和以及大于SUM/2的和都是没有意义的。把这些没有意义的和剔除掉,剩下的有意义的和的个数最多就是SUM/2个。所以,我们不需要记录S(2N,N)中都有哪些和,只需要从SUM/2到1遍历一次,逐个询问这个值是不是在S(2N,N)中出现,第一个出现的值就是答案。我们的程序不需要按照上述递推公式计算每个集合,只需要为每个集合设一个标志数组,标记SUM/2到1这个区间中的哪些值可以被计算出来。
#include <stdio.h>
int min(int a,int b)
{
return a>=b?b:a;
}
int halfsame(int array[],int n,int sum)
{
int i,j,k;
int flag[n/2+1][sum/2+1];
for(i=0;i<=n/2;i++)
for(j=0;j<=sum/2;j++)
flag[i][j]=0;//选择i个数,和为j是否存在
flag[0][0]=1;
for(k=0;k<n;k++)//元素(0 ~ N-1)
for(i=min(k+1,n/2);i>0;i--)//选择i个数(1~n/2)
for(j=1;j<=sum/2;j++)//和为j(1~sum/2)
if(j>=array[k]&&flag[i-1][j-array[k]])
flag[i][j]=1;
for(j=sum/2;j>0;j--)
{
if(flag[n/2][j]>0)
return sum-2*j;
}
}
int main()
{
int s=0,num,i,result;
int str[]={1,5,7,8,9,6,3,11,20,7};
num=sizeof(str)/sizeof(int);
for(i=0;i<num;i++)
s+=str[i];
result=halfsame(str,num,s);
printf("difference between two half array:%d\n",result);
return 1;
}