编程之美:第二章 数字之魅 2.18数组分割

/*
数组分割:
有一个无序、元素个数为2*n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近?



分析:
题目本质是要从2n个整数中找出n个,使得他们的和尽可能地靠近所有整数之和的一半。

解法1:
先将数组的所有元素排序为:a1<a2<...<a2n
将它们划分为两个子数组S1 = [a1,a3,...,a2n-1]和S2 = [a2,a4,a6,...,a2n]
从S1和S2中找出一对数进行交换,使得SUM(S1)和SUM(S2)之间的差值尽可能的小,知道找不到可对换的。这种想法的缺陷是S1和S2并不是最优的。

解法2:
假设2n个整数之和为SUM。从2n个整数中找出n个元素的和,有三种可能:大于SUM/2,等于SUM/2,和小于SUM/2.
只可以考虑<=SUM/2的情况。
用动态规划解决该问题,可以把任务分成2*N步,第k步的定义是前k个元素中任意i个元素的和,所有可能的取值之集合为Sk(只考虑<=SUM/2的情况)
然后将第k步拆分成两个小步,即首先得到前k-1个元素中,任意i割元素,总共能有多少种取值,设这个取值集合为Sk-1 = {Vi},第2步就是令
Sk = Sk-1 U {Vi + arr[k]},即可完成第k步。
时间复杂度是O(2^N)

解法3:
解法二的拆分方法需要遍历Sk-1 = {Vi}的元素,由于Sk-1 = {Vi}的元素个数随着k的增大而增大。
原来是给定Sk-1 = {Vi},求Sk,能不能给定Sk的可能值v和arr[k],去寻找v- arr[k]是否在Sk-1={Vi}中呢?由于Sk可能值的集合大小与k无关,所以
这样设计的动态规划算法其第k步的时间复杂度与k无关。



输入:
5(元素个数的一半)
1 3 11 8 20 5 7 9 6 17
输出:(两个子数组之间的最小差值)
1

分析
1 3 11 8 20(43)
5 7 9 6 17(44)

*/

/*
关键:
1 bool dp[109][100];//isOK[i][v]表示是否可以找到i个数,使得他们的和是v,注意最大和不能超过100,2*n不能超过109
2 dp[0][0] = true;//可以找到0个数,使得他们的和是0。dp[i][v]:从前i个数中取任意k个数,这些数之和为v的取法是否存在
3 for(int k = 1 ; k <= 2 * n ; k++)//时间复杂度为O(N^2*Sum),外阶段k,在前k个数中进行选择,k = 1,2,...,2*n
4 for(int i = min(k,n); i >= 1 ; i--)//内阶段i,从这个k个数中任意选出i个数,i = 1,2,...,k
5 for(int v = 1 ; v <= iSum/2 ; v++)//状态s,有两个决策包含或不包含元素k。状态:对这i个数的和为s,s = 1,2,...,sum/2
6 if(v >= pArr[k] && dp[i-1][ v - pArr[k] ])//决策:决定这i个数的和有两种决策,一个是这i个树种包含第k个数,另一个是不包含第k个数
*/
#include <stdio.h>
#include <string.h>
const int MAXSIZE = 10000;
//bool dp[109][100];//isOK[i][v]表示是否可以找到i个数,使得他们的和是v,注意最大和不能超过1000
int min(int a,int b)
{
	return a < b ? a : b;
}

void splitArray(int* pArr,int n,int iSum)
{
	bool dp[109][100];//isOK[i][v]表示是否可以找到i个数,使得他们的和是v,注意最大和不能超过100,2*n不能超过109
	memset(dp,0,sizeof(dp));
	dp[0][0] = true;//可以找到0个数,使得他们的和是0。dp[i][v]:从前i个数中取任意k个数,这些数之和为v的取法是否存在
	for(int k = 1 ; k <= 2 * n ; k++)//时间复杂度为O(N^2*Sum),外阶段k,在前k个数中进行选择,k = 1,2,...,2*n
	{
		for(int i = min(k,n); i >= 1 ; i--)//内阶段i,从这个k个数中任意选出i个数,i = 1,2,...,k
		{
			for(int v = 1 ; v <= iSum/2 ; v++)//状态s,有两个决策包含或不包含元素k。状态:对这i个数的和为s,s = 1,2,...,sum/2
			{
				if(v >= pArr[k] && dp[i-1][ v - pArr[k] ])//决策:决定这i个数的和有两种决策,一个是这i个树种包含第k个数,另一个是不包含第k个数
				{
					dp[i][v] = true;
				}
			}
		}
	}
	int s;
	for(s = iSum/2 ; s >=1 && !dp[n][s] ;s--);//因为最终是n个元素,确定最接近的给定值sum/2的和
	printf("%d\n",iSum - 2*s);
}

void process()
{
	int n;;
	while(EOF != scanf("%d",&n))
	{
		if(n <= 0 || 2*n > MAXSIZE)
		{
			continue;
		}
		int iArr[MAXSIZE];
		int iSum = 0;
		for(int i = 1 ; i <= 2 * n ; i++)
		{
			scanf("%d",&iArr[i]);
			iSum += iArr[i];
		}
		splitArray(iArr,n,iSum);
	}
}

int main(int argc,char* argv[])
{
	process();
	getchar();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值