编程之美2.18 : 有一个无序、元素个数为n的正整数数组,要求:如何能把这个数组平均分成两个子数组,并使两个子数组之和最接近。

本文探讨如何解决将一个无序的正整数数组平均分成两个子数组,使两子数组之和最接近的问题。通过分析01背包问题,详细介绍了在处理奇数和偶数个数时的状态转移方程,并讨论了滚动数组和错误处理策略,最后提到了输出分割方案的挑战。
摘要由CSDN通过智能技术生成

注: n不一定是偶数,也可能是奇数

如题所述,这是一个经典的01背包问题,但是在实现过程中遇到了很多的麻烦,花了三个小时才搞完,特此记录下。主要是分享一下解题过程中出现的错误以及相应的解决办法,希望下回不会再犯吧。

首先,先来一个简化版问题,把约束(平均分割)去掉,题目变成  元素个数为n的正整数数组,如何能把这个数组分成两个子数组,并使两个子数组之和最接近,这里没有平均分割要求,那么就是一个简单的01背包问题,即n件物品凑不超过sum/2的值, 

定义 dp[i][j]  为前i个数凑数字j 是否可能 ,其中j的范围从1 到 sum/2, 显然 d[i][0]=1; 原数组为v,为了之后的谈论方便这里 v 的元素从1开始,但程序中仍从0开始 

状态转移方程:        dp[i][j] = dp[i-1][j-v[i]] || dp[i-1][j]         j>=v[i] 时

                                         =dp[i-1][j]                                j<v[i] 时

程序大概是这样的,我没测试不知道正确与否

for(int i=1;i<=v.size();i++)
    for(int j=1;j<=sum/2;j++)
         {
              if(j>=v[i-1]) 
                   dp[i][j] = dp[i-1][j-v[i-1]]||dp[i-1][j];
             else  dp[i][j]=dp[i-1][j];
         }


现在回到原问题,先简化一点,设n是偶数。 那么,现在变成从n个里选 n/2个数,使他们之和接近 sum/2,实际上这就是多了一维的限定条件, 变成了二维的背包,定义 d[n][i][j] 为从 前 n 个数里选 i 个数使他们和为 j是否可能 

状态转移方程:        dp[n][i][j] = dp[n-1][i-1][j-v[n]] || dp[n-1][i-1][j]         j>=v[n] 时

                                                 =dp[n-1][i-1][j]                                j<v[i] 时

写成程序也很好写,但是这里是o(n^3)的空间复杂度(由于第三维j与sum有关,所以实际上会高的多),可以利用一些技巧(有些地方称这种方法为滚动数组)来简化空间复杂度,关于这一部分可以去研究背包九讲。下面这个函数返回两个子数组的最小差值

int divide_array(const vector<int> &v)
{
    const int len =v.size();
    if(!len) return 0;
    int sum=0;        //假设sum不会溢出
    for(int i=0;i<len;i++)
        sum+=v[i];
    int  h_sum = sum>>1; //即  sum/2
    int  h_len = len>>1;
    vector<vector<bool> > dp(h_len+1,vector<bool>(h_sum+1,0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值