编程之美2.18—数组分割

题目:

有一个没有排序,元素个数为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/2最接近的那个和,这便是答案。这个算法的时间复杂度是O(2^N).

因为这个过程中只关注和不大于SUM/2的那个子数组的和。所以集合中重复的和以及大于SUM/2的和都是没有意义的。把这些没有意义的和剔除掉,剩下的有意义的和的个数最多就是SUM/2个。所以,我们不需要记录S(2N,N)中都有哪些和,只需要从SUM/2到1遍历一次,逐个询问这个值是不是在S(2N,N)中出现,第一个出现的值就是答案。我们的程序不需要按照上述递推公式计算每个集合,只需要为每个集合设一个标志数组,标记SUM/2到1这个区间中的哪些值可以被计算出来。

    #include<iostream>
    using namespace std;  

    //有一个没有排序,元素个数为2N的正整数数组。要求把它分割为元素个数为N的两个数组,并使两个子数组的和最接近。
    int arr[] = {0,1,5,7,8,9,6,3,11,20,17};
    const int N=5;
    const int SUM = 87;  

    // 模仿动态规划解0-1背包问题的策略
    int solve1()
    {
        int i , j , s;
        int dp[2*N+1][N+1][SUM/2+2];  

        /*
        用dp(i,j,c)来表示从前i个元素中取j个、且这j个元素之和不超过c的最佳(大)方案,在这里i>=j,c<=S
        状态转移方程:
        限第i个物品       不取  
        dp(i,j,c)=max{dp(i-1,j-1,c-a[i])+a[i],dp(i-1,j,c)}
        dp(2N,N,SUM/2+1)就是题目的解。
        */
        //初始化
        memset(dp,0,sizeof(dp));  

        for(i = 1 ; i <= 2*N ; ++i)
        {
            for(j = 1 ; j <= min(i,N) ; ++j)
            {
                for(s = SUM/2+1 ; s >= arr[i] ; --s)
                {
                    dp[i][j][s] = max(dp[i-1][j-1][s-arr[i]]+arr[i] , dp[i-1][j][s]);
                }
            }
        }  

        //因为这为最终答案 dp[2*N][N][SUM/2+1];
        i=2*N , j=N , s=SUM/2+1;
        while(i > 0)
        {
            if(dp[i][j][s] == dp[i-1][j-1][s-arr[i]]+arr[i])   //判定这个状态是由哪个状态推导出来的
            {
                cout<<arr[i]<<" ";    //取中arr[i]
                j--;
                s -= arr[i];
            }
            i--;
        }
        cout<<endl;
        return dp[2*N][N][SUM/2+1];
    }  

    int solve2()
    {
        int i , j , s;
        int dp[N+1][SUM/2+2];     //取N+1件物品,总合不超过SUM/2+2,的最大值是多少
        memset(dp,0,sizeof(dp));    //初始状态都为0  

        for(i = 1 ; i <= 2*N ; ++i)
        {
            for(j = 1 ; j <= min(i,N) ; ++j)
            {
                for(s = SUM/2+1 ; s >= arr[i] ; --s)    //01背包从大到小,可以省空间,即最外层的空间
                {
                    dp[j][s] = max(dp[j-1][s-arr[i]]+arr[i] , dp[j][s]);
                }
            }
        }
        //要求最优解则 空间不能优化,
        return dp[N][SUM/2+1];
    }  

    int solve3()
    {
        int i , j , s;
        int isOK[N+1][SUM/2+2]; //isOK[i][v]表示是否可以找到i个数,使得它们之和等于v
        memset(isOK,0,sizeof(isOK));    //都不合法
        //注意初始化
        isOK[0][0] = 1; //可以,取0件物品,总合为0,是合法的  

        for(i = 1 ; i <= 2*N ; ++i)
        {
            for( j = 1 ; j <= min(i,N) ; ++j)
            {
                for(s = SUM/2+1 ; s >= arr[i] ; --s) //从大到小,数组少了一维
                {
                    if( isOK[j-1][s-arr[i]] )
                        isOK[j][s] = 1;
                }
            }
        }
        for(s = SUM/2+1 ; s >= 0 ; --s)
        {
            if(isOK[N][s])
                return s;
        }  

        //要求最优解则空间不能优化
        return 0;
    }  

    int main(void)
    {
        int s1 = solve1();
        int s2 = solve2();
        int s3 = solve3();
        cout<<"s1="<<s1<<endl;
        cout<<"s2="<<s2<<endl;
        cout<<"s3="<<s3<<endl;
        system("pause");
        return 0;
    }  

solve1和solve2的时间复杂度为O(2^N),

solve3的时间复杂度为O(N^2*SUM).

扩展问题:  交换两个数组元素使两个数组和的差最小

有两个数组a、b,大小都为n,数组元素的值任意整形数,无序;

要求:通过交换a、b数组中的元素,使[数组a元素的和]与[数组b元素的和]之间的差最小。

其实这个问题就是上面问题的变形,将a、b两个数组合并为一个数组,然后问题就转化为将2*n个元素数组分割为2个长度为n的数组,并使两个子数组的和最接近。

另外,特别注意:如果数组中有负数的话,上面的背包策略就不能使用了(因为第三重循环中的s是作为数组的下标的,不能出现负数的),需要将数组中的所有数组都加上最小的那个负数的绝对值,将数组中的元素全部都增加一定的范围,全部转化为正数,然后再使用上面的背包策略就可以解决了。

时间: 2024-08-24 06:39:04

编程之美2.18—数组分割的相关文章

编程之美2.18 数组分割 原创解O(nlogn)的时间复杂度求解:

题目:有一个无序.元素个数为2n的正整数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近? 1 1 2 -> 1 1 vs  2 看题时,解法的时间复杂度一般都大于或等于O(n^2).突然灵感一闪,发现一个新的解法,应该算是一个动态规划的过程吧,思路比较简单,请看代码.空间复杂度O(nlogn),时间复杂度O(n).但是不能确定是否适用所有正整数组,如果有错,请给出你的测试用例,谢谢! 代码如下: 1 1 #include <iostream> 2 2 #

编程之美2.17 数组循环移位

问题描述: 设计一个算法,把一个含有N元素的数组循环左移或者右移K位. 解决方法: 1. 暴力解法------O(KN) 2. 颠倒位置------O(N) 具体思路和代码: 1. 暴力解法------O(KN) 思路:循环K次,每次移动一位 代码: 1 //右移 2 void s1(int A[], int n, int k) 3 { 4 k = k % n; 5 for(int i = 0; i < k; i++) 6 { 7 int t = A[n-1]; 8 for(int j = n-

编程之美6:数组循环移位

楼主又来~(≧▽≦)/~啦啦啦,科研,就是要这么一鼓作气.额,其实楼主的老本行是推公式啊,做这些算法题,其实是楼主在偷懒.额,话不多说了,快请出我们今天的主角吧!还是关于数组的-数组循环移位. 下面我们来看下题目的要求. 题目要求: 设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为O(N),且只允许使用两个附加变量. 题目解答 我们来自己给个例子,来帮助自己思考.如数组为[1, 2, 3, 4, 5, 6, 7, 8],循环移位4次. 原始序列:[1, 2, 3, 4, 5,

编程之美2.17—数组循环移位(旋转数组)

题目: 把一个含有N个元素的额数组循环右移K位,要求时间复杂度O(N),且只允许使用两个附加变量. 解法一:O(N^2) 每次将数组中的元素右移移位,循环K次.当K>N时,右移K位和右移K%N位是一样的. MyShift(int a[],int N,int K) { K%=N; while(K--) { int t=a[N-1]; for(int i=N-1;i>0;i--) a[i]=a[i-1]; a[0]=t; } } 解法二:O(N) 假如数组abcd1234 1.逆序abcd:abc

编程之美 2.18数组分割

1.插入法 import java.util.*; public class Main{ public static void main(String[] args) { int[] nums={1,5,7,8,9,6,3,11,20,17}; int N=5; ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>(); for(int i=0;i<=N;i++){

编程之美2.14 求数组的子数组之和的最大值

问题描述: 一个有N个整数元素的一维数组(A[0], A[1], A[2],...,A[n-1]),这个数组当然有很多子数组,那么子数组之和的最大值是什么呢? 解法: 1. 暴力解法-------O(N^3) 2. 改进版暴力解法-------O(N^2) *3. 分治算法-------O(NlogN)(暂时未去实现) 4. 数组间关系法-------O(N) 具体思路和代码: 1.暴力解法 思路:Sum[i,...,j]为数组第i个元素到第j个元素的和,遍历所有可能的Sum[i,...,j].

编程之美2.17之数组循环移位

题目描述:设计一个算法,把一个含有N个元素的数组循环右移K位,要求算法的时间复杂度位O(Log2N),且只允许使用两个附加变量. 什么意思呢,就是说如果输入序列为:abcd1234,右移2位即变为34abcd12.唯一的要求就是使用两个附加变量. 其实这道题编程珠玑上面也出现过,书中给出的一种符合题意的解法是巧妙地进行翻转.以把abcd1234右移4位为例: 第一步:翻转1234,abcd1234---->abcd4321 第二步:翻转abcd,abcd4321---->dcba4321 第三

编程之美2.13 子数组最大乘积

问题描述: 给定一个长度为N的整数数组,只允许用乘法,不能用除法,计算任意(N-1)个数的组合乘积中最大的一组,并写出算法的时间复杂度. 解法: 1.暴力解法------O(n^2) 2.前后缀法------O(n) 3.统计法--------O(n) 具体思路和代码: 1.暴力解法: 思路:利用两层循环,依次删掉一个,其余的做乘法,计算出最大的. 代码: 1 int s1(int A[], int n) 2 { 3 int s = 1; 4 int max; 5 for(int i = 1;

【目录】编程之美

编程之美 2.1 二进制数中1的个数 2.2 阶乘 2.4 1的数目 2.5 寻找最大的k个数 2.6 精确表达浮点数 2.7求最大公约数 2.8 找符合条件的整数 2.10 求数组中最大的数和最小的数 2.12快速寻找满足条件的两个数 2.13 子数组的最大乘积 2.14 求数组的子数组之和的最大值 2.15 子数组之和的最大值(二维) 2.16 求数组的最大递增子序列 2.17 数组循环位移 2.18 数组分割 2.19 区间重合判断 2.20程序理解和时间分析 2.21 只考加法的面试题