算法之--数组分割

题目来源:编程之美2.18

有一个无序的,元素个数为2n的正整数的数组,要求:

如何能把这个数组分割为元素个数为n的两个数组,使得两个子数组的和尽量接近。


解析:因为两个子数组的和是一定的,等于整个数组的和。现在要求使得两个字数组的和尽量的接近,也就意味着要从其中选出n个数使得这n个数的和尽可能的接近sum/2,不妨设为从小于sum/2的方向接近。于是,这就是一个01背包的问题:

现在有2N个物品,每个物品的重量为A[i],有一个背包的大小为sum/2,现在从中挑选出N个物品,使得背包尽可能的被装满。

于是定义递推式为:

dp[i][j][v] = max(dp[i-1][j][v], dp[i-1][j-1][v-A[i]]+A[i]);

dp[i][j][v] :从前i个物品中选择j个,重量不大于v的最大的和。

//01 bag Problem
//print optimal solution
int minDev2(const vector &A)
{
    const int N = A.size();
    const int n = N/2;

    int sum= accumulate(A.begin(), A.end(),0);
    //dp[i][j][v] : 从i个数中选出k个数的和不超过v但是最大的和
    
    int dp[N+1][n+1][sum/2+1];

    fill_n(&dp[0][0][0],(N+1)*(n+1)*(sum/2+1),0);
    dp[0][0][0] = 0;

    for(int i=1; i<=N; ++i) //A[i-1]
    {
      for(int j=1; j <= min(n,i); ++j)//select j numbers
      {
        for(int v = A[i-1]; v <= sum/2; ++v) // minimum sum is A[i-1]
        {
          dp[i][j][v] = max(dp[i-1][j-1][v-A[i-1]]+A[i-1],dp[i-1][j][v]);
        }
      }
    }
    //print result
    int i=N,j=n,v=sum/2;
    while(i>0)
    {
        if(dp[i][j][v] == dp[i-1][j-1][v-A[i-1]]+ A[i-1])
        {
          cout << A[i-1] << "\t";
          --j;
          v -= A[i-1];
        }
        --i;
    }
    cout << endl;
    return sum-dp[N][n][sum/2];
}

上述print部分是在打印其中的一个子数组,返回的是最终的两个数组的最小的差值。

时间复杂度为: O(N*N*sum)


拓展:如果上述代码只是要求计算最终的差值,而不需要打印出结果数组的话,那么我们就可以将时间复杂度降低到N*sum.

代码为:

int minDev(const vector &A)
{
    const int N = A.size();
    if( N < 2) return 0;
    const int n = N/2;

    int sum = accumulate(A.begin(),A.end(),0);
    cout << "sum : " << sum << endl;

    bool f[n+1][sum/2+1];
    fill_n(&f[0][0],(n+1)*(sum/2+1),false);
    
    f[0][0] = true; // 选取0个数的和为0.

    for(int i=1; i<=N; ++i) //依次考察每一个数,选或者不选
    {
        //for(int j = 1; j <= min(n,i); ++j) //选择的数的个数,只能最多选n/2个。
        for(int j=min(n,i); j >=1; --j) //此处必须从后往前计算,因为在考察第i的元素的时候,用到的是第i-1个元素的第j-1行的元素。避免覆盖。
        {
            for(int s = 1; s <= sum/2; ++s) //选取数的和
            {
                //能不能选择i。
                if(s >= A[i-1] && f[j-1][s-A[i-1]])
                {
                    f[j][s] = true;
                }
                  
            }
        }
    }
    int v = sum/2;
    while(v >= 0 && !f[n][v])--v;
    cout << "v : " << v << endl;
    cout << "ret : " << sum - 2*v << endl;
    return sum-2*v;
}

最终的结果是f[N][v]==true的最大的v的值即为所求。(v是从sum/2开始依次减小)。



你可能感兴趣的:(算法)