最大子串和

来源:互联网 发布:淘宝的销量多久清零 编辑:程序博客网 时间:2024/05/22 07:41

又一个经典问题,对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。(array[]大概有1百万个数)

#include<stdio.h>int main(){    int t,n,x,i,j,sum,max;    scanf("%d",&t);    for(i=1;i<=t;i++){        scanf("%d",&n);        max=-101;        sum=0;        for(j=1;j<=n;j++){            scanf("%d",&x);            sum+=x;            if(sum>max)                max=sum;            if(sum<0)                sum=0;        }        printf("%d\n",max);    }    return 0;}

总结:Kadane算法。(Kadane算法正确性的证明以及解题思路详见:最大子串和)

(1)子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为n的数组,其含有2^n个子序列和n(n+1)/2个子串。如果使用穷举法,则至少需要O(n^2)的时间才能得到答案。卡耐基梅隆大学的Jay Kadane的给出了一个线性时间算法,我们就来看看,如何在线性时间内解决最大子串和问题。

(2)Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。(算法思想看不懂的话,就跟着程序走一遍,读完程序后就可以理解这个算法了)。

(3)当全部元素均为负数时,此写法依然成立,此时最大的子串和就是全部元素中的最大值。






(2014.7.7)今天在网上浏览最大子矩阵和的时候,又看到了一种最大子串和的解释,而且讲的还不错: 最大子矩阵和、最大子串和问题

1、直接穷举法。当n很大时,时间复杂度太高,不可行(需改进)   (这里就不再写代码了)

2、带记忆的递推法。时间复杂度较第一种方法已经有了很大的改进,但是依然比较高(当n特别大时),时间复杂度是n*n(还需改进);还有一点就是边界初始化问题,见注释信息(b[-1])    (附上此种方法代码,依然超时)

#include<iostream>#include<cstdio>using namespace std;int a[1000005],b[1000005]; //a[]为原数组,b[j]为a[0]到a[j]的和int main(){    int t,n,i,j,sum,max;    scanf("%d",&t);    while(t--){        scanf("%d",&n);        for(i=0;i<n;i++)            scanf("%d",&a[i]);        b[0]=a[0];        for(i=1;i<n;i++)            b[i]=b[i-1]+a[i];        max=-101;        for(i=0;i<n;i++)            for(j=i;j<n;j++){                sum=b[j]-b[i-1]; //语法上没有错误,b[-1]也被访问到了,并参与了运算                if(sum>max)                    max=sum;            }        printf("%d\n",max);    }    return 0;}

3、DP。下面我们来分析一下最大子段和的子结构,令b[j]表示从a[0]~a[j]的最大子段和,b[j]的当前值只有两种情况:

(1) 最大子段一直连续到a[j]    (2) 以a[j]为起点的子段,不知有没有读者注意到还有一种情况,那就是最大字段没有包含a[j],如果没有包含a[j]的话,那么在算b[j]之前的时候我们已经算出来了,注意我们只是算到位置为j的地方,所以最大子断在a[j]后面的情况我们可以暂时不考虑。

由此我们得出b[j]的状态转移方程为:b[j]=max{b[j-1]+a[j],a[j]} ,所求的最大子断和为max{b[j],0<=j<n} 。进一步我们可以将b[]数组用一个变量代替。

#include<iostream>#include<cstdio>using namespace std;int a[1000005];int maxSubArray(int a[],int n){    int b=0,sum=-10000000;  //sum=INT_MIN ,INT_MIN包含在#include<climits>表示无穷小(相应的也有INT_MAX)    for(int i=0;i<n;i++){        if(b>0)            b+=a[i];        else            b=a[i];        if(b>sum)            sum=b;    }    return sum;}int main(){    int t,n,i;    scanf("%d",&t);    while(t--){        scanf("%d",&n);        for(i=0;i<n;i++)            scanf("%d",&a[i]);        printf("%d\n",maxSubArray(a,n));    }    return 0;}


就个人来看,感觉第三种DP跟上面的Kadane算法好像是一样的。恩,,,,,,,,,  再好好琢磨琢磨,体会体会。。。。。









0 0