数组分割算法

今天读编程之美遇到一道题,跟着其中的算法思路进行了推理思考,让我重新温习了高中的数学知识,梳理了一边自己的数学思维,颇有感触,不记可惜,特发此文。
题意如下:
 有一个没有排序,元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近?
 遇到任何题目,我们都应该将思路从简至繁,复杂度从繁至简地思考解决办法,即,先找傻子都能想到的办法,然后步步优化,那么可得第一种办法:
解法一
枚举所有n个数的组合,求出每组的解,然后从中找出最接近数组总和一半的组合,即为解,复杂度为C(2n,n)。
唔。。假设n=25,我用组合数计算器算了一下C(50,25) = 126410606437752,要运行126410606437752*24次加法,即使是每秒运算5.49亿次的天河二号计算机,也要消耗6067704秒,明显是不靠谱的。

然后,如何化简算法呢,我们要摒弃枚举,就要在每一步的运算中,能用得上历史运算结果,在历史运算结果的基础上,得到新的结果,这样不断更新结果的方法,一定比枚举法要高效,是的,我说的就是动态规划,解法如下:
解法二
设数组为arr,数组和为SUM,再设一个辅助空间heap堆,其中heap[i]用来表示存储从数组arr中取i个数所能产生的和之集合的堆,注意heap里存放的是一个个集合。初始化heap[0]={0},设置为动态规划的边界条件,然后遍历数组,不断更新heap,最终得到heap[n]中所有集合。伪代码如下:

for(k=1;i<=2*n;k++){//遍历数组 2n次
    i_max=min(k-1,n-1);//上限取到n-1,因为我们最多只求一半数量的组合
    for(i=i_max;i>=0;i--)//遍历i_max, O(k)次
    {
        foreach v in heap[i]//遍历heap[i]的集合
            insert(v+arr[k],heap[i+1]);//在heap[i]中集合的每个元素的基础上,加上arr[k]值,更新heap[i+1]
    }
}

这个算法要注意的一点就是遍历i_max的时候,是倒着遍历的,因为如果正着遍历,由heap[0]更新完heap[1]后,heap[1]中的和就包含了arr[k],再由heap[1]更新heap[2],又要包含一次arr[k],这就重复了,会导致错误的结果。
这个算法本质上就是在历史heap中,对每个集合都与arr[k]结合,更新整个heap,每一次更新都使得heap翻倍,因此运算次数为2^2n,复杂度为O(4^N)。时间复杂度是N的指数级,因此在N很大时,效率很低,我们不得不考虑设计一种时间复杂度是N的多项式函数的方法。考虑的出发点是,是否有另一种拆分第K步的方法。
解法三
解法二的拆分方法需要不断遍历历史heap堆,由于heap随着k的增大而增大,所以导致了解法二的效率低下,能不能设计一个算法使得第k步花费的时间与k无关呢?我们不妨倒过来想,原来是给定旧heap,求新heap。那我们能不能给定新heap的可能值v和arr[k],去寻找v-arr[k]是否在旧heap中呢?由于新heap可能值的集合的大小与k无关,所以新算法第k步的时间复杂度与k无关。
代码如下:

定义:isOk[i][v]表示是否可以找到i个数,使得他们之和等于v
初始化isOk[0][0]=true;
for(k=1;k<=2*n;k++)
{
    for(i=1;(i<=k && i<=n);i++)
        for(v=1;v<=Sum/2;v++)
            if(v>=arr[k]&&isOk[i-1][v-arr[k]])
                isOk[i][v]=true;
}

利用如上的算法,时间复杂度将为O(N^2*SUM)。
虽然解法三对于N是多项式的时间算法,但当SUM很大而N很小时,这个算法是很不合适的,所以我们应该根据具体的问题选择最适合的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值