将某数组划分为两个子数组,使得两个子数组各元素之和a,b的差最小

https://www.nowcoder.com/practice/aea0458d54d74f3ca14012cbdf249918?tpId=40&tqId=30986&tPage=2&rp=2&ru=/ta/kaoyan&qru=/ta/kaoyan/question-ranking

这是题目链接。是上海交通大学研究生机试题。

时间限制:C/C++ 1秒,其他语言2秒 

空间限制:C/C++ 32M,其他语言64M

题目描述

一个数组中有若干正整数,将此数组划分为两个子数组,使得两个子数组各元素之和a,b的差最小,对于非法输入应该输出ERROR。

输入描述:

数组中的元素

输出描述:

降序输出两个子数组的元素和

示例

输入

10 20 30 10 10
10 20 abc 10 10

输出

40 40
ERROR

这道题目有三点让人难受:

一个是输入,处理字符串总是让人难受;

二是不知道数组的大小,只能往大了设,同时选择具体算法时就很难受,要是没经过试探之前,我也不可能往DFS上想,因为我觉得比起0-1背包,怎么样都比遍历每一个数的复杂度低。结果被数组越界打脸了。

三是不知道每个数的大小,就像我在二里面提到的,因为数太大了,开数组的时候以为够了,结果越界了。

 

 

 先说一下大概思路。设分为两个数组后,这两个数组元素和分别sum1,sum2。这里的差是指绝对值,也就是大的减去小的。在这里,我们先假设sum1≥sum2。那么要求的就是sum1-sum2的值最小。

设sum=sum1+sum2。可以知道,sum的大小是不变的,它就是未划分之前所有元素的和。

那么sum1-sum2=sum1+sum2-2*sum2=sum-2*sum2。这里就很明显了,因为sum是不变的,我们只要求出满足条件的最大的sum2。这里要注意到sum2满足sum1≥sum2这个条件。那么只用满足sum2≤sum/2即可。

题意简化为从一个数组中选出若干数满足这些数的和最大并且小于等于k。(其中k=sum/2,因为数组确定后sum就确定了,所以不妨假设这是一个常数)。

先说一下我刚开始的想法(可跳过这段)。我最先想到的是最大子序列和的改进版,只要dp时再满足dp[i]≤k就好了。仔细思考就会发现这个想法的错误性。因为在更新dp[i]时前面每一个dp[1~i-1]都只保存了局部最优。稍微举几个例子就知道了,假如满足条件的最大子序列和是a[3]+a[4]。但是dp[3]可能是a[1]+a[2]+a[3],而更新dp[4]时a[4]+dp[3]>k,这样就不可能找得到要求的子序列。

那么仔细思考就会发现,这就是0-1背包问题。动态规划在理论上是完全可行的。如果题目给出的数据范围合适,这确实可行,可惜爆内存了。因为它给的整数可能太大了,我们用一个数组来保存和更新是完全不可行的。

那么,就是用递归函数来实现,理论上用dfs来进行遍历得到所有可能的小于等于k的和再选出最大的就可以了。但是我试过了,会超时(不排除我写错了)。但是,但是,如果进行剪枝,就AC了。

先对得到的数组排序,从大到小排序。然后在dfs过程中实时更新找到的满足条件的最大子序列和ans,那么考虑第i个数的时候,假如继续往下搜索不可能比目前找到的最大子序列ans大,那就无需接着往下遍历。 

这题的话我觉得它是让我们考虑算法的优化。一般来说dp的复杂度会好一点,而DFS在这里基本上是指数级,但是考虑到实际情况,对DFS剪枝后就(竟然)能过。相当于具体问题具体分析吧。

我也说不出什么,直接贴AC代码吧。

#include
#include
int handle(char s[],int a[]){//这个函数是用来处理字符串的
    int count=0;//计算处理完字符串后有几个数字(即整数数组大小)
    int i=0;
    while(s[i]!='\n'){//因为用的是fgets,所以字符串最后一个字符是换行符(当然,换行符后面还有字符串结尾表示符'\0')。
        if(s[i]>='0'&&s[i]<='9'){//假如我们遇到了数字,那么接下来处理这个数字
            while(s[i]>='0'&&s[i]<='9'){//可以用while直接处理完这串数字
                a[count]=a[count]*10+(s[i]-'0');
                i++;
            }
            count++;//整数数量加一
            continue;//注意,处理到非数字后才结束,所以接下来这个字符未被判断是否合法,所以跳过下面的i++过程
        }
        else if(s[i]!=' '&&s[i]!='\n')return 0;//处理完数字,剩下的只可能是空格,注意,还可能是最后的换行符。如果不是这两个,那么就是非法的,返回0
        i++;//在不是数字的情况下才可能i++,与上面的continue相对应
    }
    for(int i=0;i=high)return;
    int i=low,j=high;
    int val=a[i];
    while(ival)i++;
        a[j]=a[i];
    }
    a[i]=val;
    qs(low,i-1,a);qs(i+1,high,a);
}

int max(int a,int b){//返回两个数中的较大值
    return a>b?a:b;
}
int a[50000];//为了方便写DFS,我把这些写为全局变量。全局变量还有一个好处就是,数组可以开的稍微大点
int ans;//用来存储小于等于sum/2的最大子序列的和
int n;//整数数组的大小
int half;//half=sum/2
void dfs(int i,int sum){
    if(i>=n||sum+(n-i)*a[i]

总结一下,这道题通过率低有两点:

  1. 用了0-1背包的思路,但是数组越界(爆内存)了。
  2. 用了DFS,但是没有剪枝优化。

你可能感兴趣的:(将某数组划分为两个子数组,使得两个子数组各元素之和a,b的差最小)