[经典面试题][网易]数组分割

简介:

【题目】

任意2N个正整数,从其中选出N个整数,使得选出的N个整数和同剩下的N个整数之和的差最小。

【来源】

网易

【分析】

假设数组A[1..2N]所有元素的和是SUM。模仿动态规划解0-1背包问题的策略。

从2N个数中找N个元素,有三种可能:大于Sum/2,小于Sum/2以及等于Sum/2。而大于Sum/2与小于等于Sum/2没区别,故可以只考虑小于等于Sum/2的情况。

令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最接近的那个和,这便是答案。

【分析一】

状态转移方程:


其中,S[i-1][j][k]表示前i-1个元素中选取j个使其和不超过但最逼近k;

         S[i-1][j-1][k-A[i]]在前i-1个元素中选取j-1个元素使其和不超过但最逼近k-A[i],这样再加上A[i]即第i个元素就变成了 选择上第i个元素的情况下最逼近k的和。

而第一种情况与第二种情况是完备且互斥的,所以需要将两者最大的值作为F[i][j][k]的值。

可以设置一个三维数组Path[][][]来记录所选择元素的轨迹。根据求得的Path[][][]我们可以从S[2N][N][Sum/2]往S[0][0][0]逆着推导来打印轨迹对应的元素。

该算法的时间复杂度为O(n^2*sum),空间复杂度也为O(n^2*sum)。

【代码一】

    /*********************************
    *   日期:2015-02-01
    *   作者:SJF0115
    *   题目: 数组分割
    *   来源:网易
    *   博客:
    **********************************/
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;

    // 模仿动态规划解0-1背包问题的策略
    int MinSum(int num[],int n){
        if(n <= 0){
            return 0;
        }//if
        int sum = 0;
        int size = 2*n;
        // the sum of all number
        for(int i = 1;i <= size;++i){
            sum += num[i];
        }//for
        int dp[size + 1][n + 1][sum / 2 + 1];
        // 选中数字路径
        int path[size + 1][n + 1][sum / 2 + 1];
        memset(dp,0,sizeof(dp));
        memset(path,0,sizeof(path));
        // 用dp(i,j,k)来表示从前i个元素中取j个元素,使得其和不超过k且最接近k,j <= min(i,n),k <= Sum/2
        // 状态转移方程:  
        // dp(i,j,k)= max{dp(i-1,j-1,k-num[i])+num[i],dp(i-1,j,k)}
        // dp(2N,N,SUM/2+1)就是题目的解。
        // 前i个元素
        for(int i = 1;i <= size;++i){
            // 任选j个元素
            int count = min(i,n);
            for(int j = 1;j <= count;++j){
                // 使得其和不超过k且最接近k
                for(int k = 1;k <= sum / 2;++k){
                    // dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-1][k-num[i]] + num[i]);
                    // 是否选第i个元素
                    dp[i][j][k] = dp[i-1][j][k];
                    if(k >= num[i] && dp[i-1][j][k] < dp[i-1][j-1][k-num[i]] + num[i]){
                        dp[i][j][k] = dp[i-1][j-1][k-num[i]] + num[i];
                        path[i][j][k] = 1;
                    }//if
                }//for
            }//for
        }//for
        // 打印选择路径
        int i = 2*n,j = n,k = sum / 2;
        while(i > 0 && j > 0, k > 0){
            if(path[i][j][k] == 1){
                cout<<num[i]<<" ";
                --j;
                k -= num[i];
            }//if
            --i;
        }//while
        cout<<endl;
        int sum1 = dp[size][n][sum/2];
        int sum2 = sum - dp[size][n][sum/2];
        cout<<"Sum1->"<<sum1<<" sum2->"<<sum2<<endl;
        return abs(sum1 - sum2);
    }

    int main(){
        //int A[] = {0,1,5,7,8,9,6,3,11,20,17};
        //int A[] = {0,1,2,3,4,5,6,7,8,9,10};
        int A[] = {0,2,8,5,10};
        int n = 2;
        cout<<"最小差->"<<MinSum(A,n)<<endl;;
        return 0;
    }


【分析二】

我们从上面的代码可以看出,S[i][j][k]只与 S[i-1][][]有关,这一点状态方程上也能反映出来。这种固定关系我们可以想办法去掉。从而节省空间。

我们可以用二维数组来代替三维数组来达到降低空间复杂度的目的。

但是怎么代替呢?因为S[i][j][k]只与S[i-1][][]有关,可以把i这一维省略。

同时用二维数组来代替的时候应该对S[i][j][k]的“j”维进行逆序遍历。

为 什么? 因为只有这样才能保证计算S[i][j][k]时利用的S[i-1][j][]和S[i-1][j-1][]是真正i-1这个状态的值,如果正序遍 历,那么当计算S[][j][]时,S[][j-1][]已经变化,那么计算的结果就是错误的。

【代码二】

    /*********************************
    *   日期:2015-02-01
    *   作者:SJF0115
    *   题目: 数组分割
    *   来源:网易
    *   博客:
    **********************************/
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;

    int MinSum(int num[],int n){
        if(n <= 0){
            return 0;
        }//if
        int sum = 0;
        int size = 2*n;
        // the sum of all number
        for(int i = 1;i <= size;++i){
            sum += num[i];
        }//for
        int dp[n + 1][sum / 2 + 1];
        // 选中数字路径
        int path[size+1][n + 1][sum / 2 + 1];
        memset(dp,0,sizeof(dp));
        memset(path,0,sizeof(path));
        //
        for(int i = 1;i <= size;++i){
            // 任选j个元素
            int count = min(i,n);
            for(int j = count;j >= 1;--j){
                // 使得其和不超过k且最接近k
                for(int k = num[i-1];k <= sum / 2;++k){
                    if(dp[j][k] < dp[j-1][k-num[i-1]] + num[i-1]){
                        dp[j][k] = dp[j-1][k-num[i-1]] + num[i-1];
                        path[i][j][k] = 1;
                    }//if
                }//for
            }//for
        }//for
        // 打印选择路径
        int i = 2 * n,j = n,k = sum / 2;
        while(i > 0 && j > 0 && k > 0){
            if(path[i][j][k] == 1){
                cout<<num[i]<<" ";
                --j;
                k -= num[i];
            }//if
            --i;
        }//while
        cout<<endl;
        int sum1 = dp[n][sum/2];
        int sum2 = sum - dp[n][sum/2];
        cout<<"Sum1->"<<sum1<<" sum2->"<<sum2<<endl;
        return abs(sum1 - sum2);
    }

    int main(){
        int A[] = {0,1,5,7,8,9,6,3,11,20,17};
        //int A[] = {0,1,2,3,4,5,6,7,8,9,10};
        //int A[] = {0,2,8,5,10};
        int n = 5;
        cout<<"最小差->"<<MinSum(A,n)<<endl;;
        return 0;
    }

【思路三】

    /*-------------------------------------
    *   日期:2015-02-01
    *   作者:SJF0115
    *   题目: 数组分割
    *   来源:网易
    *   博客:
    ------------------------------------*/
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;

    int MinSum(int num[],int n){
        if(n <= 0){
            return 0;
        }//if
        int sum = 0;
        int size = 2*n;
        // the sum of all number
        for(int i = 1;i <= size;++i){
            sum += num[i];
        }//for
        // isOK[i][v]表示是否可以找到i个数,使得它们之和等于v
        int isOK[n + 1][sum / 2 + 1];
        int path[size + 1][n + 1][sum / 2 + 1];
        // 都不合法
        memset(isOK,0,sizeof(isOK));
        memset(path,0,sizeof(path));
        // 注意初始化
        // 可以,取0件物品,总合为0,是合法的
        isOK[0][0] = 1;
        for(int i = 1;i <= size;++i){
            int count = min(i,n);
            for(int j = 1;j <= count;++j){
                // 从大到小,数组少了一维
                for(int k = sum / 2;k >= num[i];--k){
                    if( isOK[j-1][k-num[i]] ){
                        isOK[j][k] = 1;
                        path[i][j][k] = 1;
                    }//if
                }//for
            }//for
        }//for
        // 打印选择路径
        int i = 2 * n,j = n,k = sum / 2;
        while(i > 0 && j > 0 && k > 0){
            if(path[i][j][k] == 1){
                cout<<num[i]<<" ";
                --j;
                k -= num[i];
            }//if
            --i;
        }//while
        cout<<endl;
        //
        int minNum;
        for(int k = sum / 2;k >= 0;--k){
            if(isOK[n][k]){
                minNum = k;
                break;
            }//if
        }//for
        int sum1 = minNum;
        int sum2 = sum - minNum;
        cout<<"Sum1->"<<sum1<<" sum2->"<<sum2<<endl;
        return abs(sum1 - sum2);
    }

    int main(){
        //int A[] = {0,1,5,7,8,9,6,3,11,20,17};
        //int A[] = {0,1,2,3,4,5,6,7,8,9,10};
        int A[] = {0,2,8,5,10};
        int n = 2;
        cout<<"最小差->"<<MinSum(A,n)<<endl;;
        return 0;
    }





目录
相关文章
|
1月前
|
算法
【数组相关面试题】LeetCode试题
【数组相关面试题】LeetCode试题
|
1月前
|
存储
力扣面试经典题之数组/字符串
力扣面试经典题之数组/字符串
23 0
|
2月前
|
算法 前端开发
经典面试题:扁平化嵌套数组
经典面试题:扁平化嵌套数组
22 0
|
3月前
|
存储 JavaScript 前端开发
【面试题】JS的14种去重方法,看看你知道多少(包含数组对象去重)
【面试题】JS的14种去重方法,看看你知道多少(包含数组对象去重)
|
3月前
|
前端开发 JavaScript Java
【面试题】说说 JavaScript数组常见的操作 (20个)
【面试题】说说 JavaScript数组常见的操作 (20个)
|
1月前
|
算法 测试技术 索引
力扣面试经典题之数组/字符串(二)
力扣面试经典题之数组/字符串(二)
13 0
|
2月前
|
JavaScript 前端开发 索引
【JavaScript】面试手撕数组原型链(易)
续借上文,这篇文章主要讲的是数组原型链相关的考题,有些人可能会纳闷,数组和原型链之间有什么关系呢?我们日常使用的数组forEach,map等都是建立在原型链之上的。举个🌰,如我有一个数组const arr = [1,2,3]我想要调用arr.sum方法对arr数组的值进行求和,该如何做呢?我们知道数组没有sum函数,于是我们需要在数组的原型上定义这个函数,才能方便我们调用,具体代码如下。接下来我们就是采用这种方式去实现一些数组常用的方法。
39 6
|
2月前
|
JavaScript 前端开发
【JavaScript】面试手写题精讲之数组(上)
该专题主要是讲解我们在面试的时候碰到一些JS的手写题, 确实这种手写题还是比较恶心的。有些时候好不容易把题目写出来了,突然面试官冷不丁来一句有没有更优的解法,直接让我们僵在原地。为了解决兄弟们的这些困扰,这个专题于是就诞生啦。我们会将一些常见的不是最优解的答案作为对比,方便大家更好理解。
38 3
|
3月前
|
存储 算法 Java
数据结构和算法面试题:给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
数据结构和算法面试题:给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
42 0
|
3月前
|
算法 Java C++
数据结构与算法面试题:实现一个函数 fill(int[] a, int n, int v),使其将大小为 n 的数组 a 填满为 v。
数据结构与算法面试题:实现一个函数 fill(int[] a, int n, int v),使其将大小为 n 的数组 a 填满为 v。
16 0