任意2n个正整数数组,将其分割成两个长度为n的数组,使两子数组之和的差值最小

题目描述:任意2n个正整数数组,将其分割成两个长度为n的数组,使两子数组之和的差值最小。
或者:从2n个正整数中选取n个,使这n个数字之和和剩余n个数字之和的差值最小。

这道题利用动态规划进行求解,可以采用0-1背包问题的策略,放或者不放,不太了解0-1背包的,参考链接https://blog.csdn.net/qq_34826261/article/details/100663768。
假设数组的和为sum,我们的目标可以转化为求数组中n个元素之和和最接近sum/2的那个和,大于或者小于关于sum/2对称,找小于sum/2的最接近sum/2的那个和更加方便。定义f(j,k)为有j个数字,和为k的标志,即对长为2n的数组进行遍历时,是否可将当前数字放入使其构成长为j,和为k的分组,是则为true,也就是说最后得到的结果在f(n,k)=true(k<=sum/2且最接近sum/2)中,其中一个分组的和为:
(1) k = m a x { f ( n , k ) = t r u e ∣ k &lt; = s u m / 2 } k=max\{f(n,k)=true|k&lt;=sum/2\}\tag{1} k=max{f(n,k)=truek<=sum/2}(1)
先放出来更新公式:
(2) f ( j , k ) = t r u e , i f ( k &gt; = A [ i ] &amp; &amp; f ( j − 1 , k − A [ i ] ) = t r u e ) , i &lt; = 2 n f(j,k)=true,if(k&gt;=A[i]\&amp;\&amp;f(j-1,k-A[i])=true),i&lt;=2n\tag{2} f(j,k)=true,if(k>=A[i]&&f(j1,kA[i])=true),i<=2n(2)
对更新公式解释,对于任意长度的子数组的和有以下几种情况,h(i,j)表示从长i的数组中取j个元素之和的集合,
(3) h ( i , 0 ) = { 0 } h ( i , 1 ) ∈ { A [ m ] ∣ m &lt; = i } h ( i , j ) = { h ( i − 1 , j − 1 ) + A [ i ] ∣ j &lt; i } ∪ { h ( i − 1 , j ) ∣ j &lt; i } h ( i , i ) = { A [ 0 ] + A [ 1 ] + . . . + A [ i ] } h(i,0) = \{0\}\\ h(i,1) ∈ \{A[m]|m&lt;=i\}\\ h(i,j) = \{h(i-1,j-1)+A[i]|j&lt;i\}∪\{h(i-1,j)|j&lt;i\}\tag{3}\\ h(i,i) = \{A[0]+A[1]+...+A[i]\} h(i,0)={0}h(i,1){A[m]m<=i}h(i,j)={h(i1,j1)+A[i]j<i}{h(i1,j)j<i}h(i,i)={A[0]+A[1]+...+A[i]}(3)
如果对于任意的j,h(i,j)中存在k,则f(j,k) = true。对于任意f(j,k)=true,则k∈h(i,j),则必有k+A[m]∈h(i,j+1),m<i,则f(j,k+A[m]) = true,反过来,若k∈h(i,j),则必存在k-A[m]∈h(i,j-1),本段新加入的A[m]都和已有的j个元素不重复。
下面考虑(2)式,假设我们已经获取了i-1时的f(j,k),则i时如何更新f(j,k),不难发现从i-1到i仅仅多出了A[i]这一个数字,相当于(3)式的第三个式子等号右边的第一项,这时候若h(i-1,j-1)中存在k-A[i],即f(j-1,k-A[i])=true,则f(j,k) = true。
代码如下:

public int minDValueofN(int[] nums) {
	int sum = 0;
	int len = nums.length / 2;
	for(int i = 0; i < nums.length; i++) {
		sum += nums[i];
	}
	boolean[][] flag = new boolean[len+1][sum/2+1];
	flag[0][0] = true;
	for(int i = 1; i <= nums.length; i++) {
		// 注意j是逆序更新
		// 这里是因为更新flag[j][]需要使用flag[j-1][],后面的更新不会影响前面的值
		// 实际上可以做一个三维的动态规划flag[i][j][k] = flag[i-1][j-1][k-nums[i-1]]
		// 但是没有必要,可以参考一下01背包问题的优化
		// https://blog.csdn.net/qq_34826261/article/details/100663768
		for(int j = i<=len?i:len; j > 0; j--) {
			for(int k = 0; k <= sum/2; k++) {
				if(k >= nums[i-1] && flag[j-1][k-nums[i-1]]) {
					flag[j][k] = true;
				}
			}
		}
	}
	// 找出最接近sum/2的k值
	for(int k = sum/2; k > 0; k--) {
		if(flag[len][k]) {
			return Math.abs(2*k - sum);
		}
	}
	return -1;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值