六种姿势躺过最大连续子序列和

Maximum Continuous Subsequence Sum

最大连续子序列求和

问题描述:输入一个整数或者浮点数序列,求连续的子序列和的最大值

注意:不可以使用贪心,因为可能为了得到连续的最大子序列和而先取部分负数从而得到更大的序列和

比如:给定序列{-2,11,-4,13,-5,-2},其最大连续子序列为{11,-4,13},和为20

——————————————————————————————————————————————

算法一:朴素

描述:两个for循环枚举子序列的首尾,然后再来个for循环计算序列的和,每次更新最大值

时间复杂度o(n^3),基本不考虑这种算法

int mcss(int* arr,int length,int& left,int& right)
{
    int maxx=-inf,sum;
    for(int start=0; start<length; start++)
        for(int end=start; end<length; end++)
        {
            sum=0;
            for(int i=start; i<=end; i++)
                sum+=arr[i];
            if(sum>maxx)
            {
                maxx=sum;
                left=start;
                right=end;
            }
        }
    return maxx;
}

——————————————————————————————————————————————

算法二:预处理

描述:在朴素算法中,第二层循环中保存上一次搜索的序列和(如果没有上一次,那么赋0值)

加上这次遍历到的数,维护从start到当前end的序列和,从而简化了循环求和的步骤

时间复杂度o(n^2),效率也是非常糟糕,一般不考虑

int mcss(int* arr,int length,int& left,int& right)
{
    int maxx=-inf,sum;
    for(int start=0; start<length; start++)
    {
        sum=0;
        for(int end=start; end<length; end++)
        {
            sum+=arr[end];
            if(sum>maxx)
            {
                maxx=sum;
                left=start;
                right=end;
            }
        }
    }
    return maxx;
}

——————————————————————————————————————————————

算法三:分治

描述:分治法就是将给你的序列分成两部分(随便你怎么分,不过一般人都是对半分)

那么最大连续子序列出现的位置就有三种情况:

1、最大连续子序列完全在输入序列的左半部分

2、最大连续子序列完全在输入序列的右半部分

3、最大连续子序列完全跨越输入序列的左右两个部分

对于前两种情况,分别递归计算左右两块序列的最大连续子序列

对于第三种情况,从中间向两边遍历求出包含中心两个元素的最大和

边界条件是当输入序列不能再分,即序列只有一个元素或者是空集的时候

最后比较这三种情况的值

时间复杂度o(nlogn),复杂度已经不错

int mcss(int* arr,int start,int end,int& left,int& right)
{
    //边界条件是序列只有一个元素,取这个元素与0的最大值
    if(start==end)
    {
        left=right=start;
        return arr[start];
    }
    //递归求解左右两个部分的最大连续子序列和
    int mid=(start+end)>>1;
    int maxx=mcss(arr,start,mid,left,right);
    int tmpleft,tmpright;
    int tmpmax=mcss(arr,mid+1,end,tmpleft,tmpright);
    if(tmpmax>maxx)
    {
        maxx=tmpmax;
        left=tmpleft;
        right=tmpright;
    }
    //遍历左半部分的包含最右元素的最大连续子序列和
    int leftmax=arr[mid],leftsum=0;
    tmpleft=mid;
    for(int i=mid;i>=start;i--)
    {
        leftsum+=arr[i];
        if(leftsum>=leftmax)
        {
            leftmax=leftsum;
            tmpleft=i;
        }
    }
    //遍历右半部分的包含最左元素的最大连续子序列和
    int rightmax=arr[mid+1],rightsum=0;
    tmpright=mid+1;
    for(int i=mid+1;i<=end;i++)
    {
        rightsum+=arr[i];
        if(rightsum>rightmax)
        {
            rightmax=rightsum;
            tmpright=i;
        }
    }
    //求跨越两个部分的最大连续子序列和
    tmpmax=leftmax+rightmax;
    //返回三个值的最大值
    if(tmpmax>maxx||tmpmax==maxx&&tmpleft<left||tmpmax==maxx&&tmpleft==left&&tmpright<right)
    {
        maxx=tmpmax;
        left=tmpleft;
        right=tmpright;
    }
    return maxx;
}

——————————————————————————————————————————————

算法四:累积遍历

描述:设输入序列为A,长度为N,从a0开始求和并记录最大值,如a0,a0+a1,a0+a1+a2,...,直到开始出现求和小于0则停止。设加到a(p)时开始小于0,即a0,a0+a1,a0+a1+a2,...,a0+a1+a2+...+a(p-1)都大于0,而a0+a1+a2+...+a(p)小于0。此时,可以从a(p+1)开始重新求和并记录最大值

证明:

设子序列的开始为start,结束为end

那么从a0到a(p-1)之间(即start>=0而且start<=p-1)开始的子序列分为两种情况:

1、end<p。a(start)+...+a(end)=[a0+a1+...a(end)]-[a0+a1+...+a(start-1)]

而start-1<p,所以a0+a1+...+a(start-1)>0

所以a(start)+...+a(end)<a0+a1+...+a(end),而a0+a1+...+a(end)已经考虑过,所以这种情况满足

2、end>=p。因为start>=0而且start<=p-1,所以a0+a1+...+a(start-1)>0

而且a0+a1+...+a(p)<0,所以a(start)+...+a(p)<0

对于end>=p,a(start)+...+a(end)<a(p+1)+...+a(end),所以这种情况也满足

时间复杂度o(n),非常快,效率很高

int mcss(int* arr,int length,int& left,int& right)
{
    int maxx=-inf,sum=-inf,tmpleft;
    for(int i=0; i<length; i++)
    {
        if(sum<0)
        {
            sum=arr[i];
            tmpleft=i;
        }
        else sum+=arr[i];
        if(sum>maxx)
        {
            maxx=sum;
            left=tmpleft;
            right=i;
        }
    }
    return maxx;
}

——————————————————————————————————————————————

算法五:动态规划

描述:动态规划思想算法比较普遍

状态转移方程为sum[i]=max(sum[i-1]+a[i],a[i]){sum[i]记录的是以a(i)为子序列末端的最大连续和}

对于所有以a(i)为末端的子序列和sum(i),有只取a(i)和取以a(i-1)为末端的子序列和sum(i-1)加上a(i)两种情况

在dp(比较两种情况的大小)的过程中便可以维护最大值

仔细比较动态规划法和累积遍历法,发现这两种算法写法上非常相似,但是思想不同

时间复杂度o(n),复杂度不错

int mcss(int* arr,int length,int& left,int& right)
{
    int maxx=-inf,sum=-inf,tmpleft;
    for(int i=0;i<length;i++)
    {
        if(sum+arr[i]>=arr[i])sum+=arr[i];
        else
        {
            sum=arr[i];
            tmpleft=i;
        }
        if(sum>maxx)
        {
            maxx=sum;
            left=tmpleft;
            right=i;
        }
    }
    return maxx;
}


——————————————————————————————————————————————

算法六:第二种累积遍历

描述:显然有,连续子序列的和其实就是(从0到子序列末端的和)-(从0到子序列首端-1的和)

其实我们可以遍历数组,对于末端为a(i)的子序列,找出从0到子序列首端-1的和 的最小值

遍历的时候,找出以当前位置为末端,以此前的某一点为首端 的和最大的子序列更新最大值

其实o(n)的三种算法都有相似之处

时间复杂度o(n),复杂度不错

int mcss(int* arr,int length,int& left,int& right)
{
    int maxx=-inf,sum=0,minn=0,tmpleft=0;
    for(int i=0;i<length;i++)
    {
        sum+=arr[i];
        if(sum-minn>maxx)
        {
            maxx=sum-minn;
            left=tmpleft;
            right=i;
        }
        if(sum<minn)
        {
            minn=sum;
            tmpleft=i+1;
        }
    }
    return maxx;
}

——————————————————————————————————————————————


下面以hdu-1003为例给出部分算法的代码


分治

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define clr(x,y) memset(x,y,sizeof(x))
#define maxn 100000+5
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IT iterator
#define push_back PB

typedef long long ll;
const double eps = 1e-9;
const double pi  = acos(-1);
const ll mod = 1e9+7; 


int num[maxn];
int mcss(int s,int e,int& l,int& r)
{
    if(s==e)
    {
        l=r=s;
        return num[s];
    }
    int m=(s+e)>>1;
    int maxx=mcss(s,m,l,r);
    int tl,tr;
    int tmp=mcss(m+1,e,tl,tr);
    if(tmp>maxx)
    {
        maxx=tmp;
        l=tl;
        r=tr;
    }
    int lmax=num[m],lsum=0;
    tl=m;
    for(int i=m;i>=s;i--)
    {
        lsum+=num[i];
        if(lsum>=lmax)
        {
            lmax=lsum;
            tl=i;
        }
    }
    int rmax=num[m+1],rsum=0;
    tr=m+1;
    for(int i=m+1;i<=e;i++)
    {
        rsum+=num[i];
        if(rsum>rmax)
        {
            rmax=rsum;
            tr=i;
        }
    }
    tmp=rmax+lmax;
    if(tmp>maxx||tmp==maxx&&tl<l||tmp==maxx&&tl==l&&tr<r)
    {
        maxx=tmp;
        l=tl;
        r=tr;
    }
    return maxx;
}
int main()
{
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    cin>>T;
    for(int fir=1;fir<=T;fir++)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d",&num[i]);
        int l,r;
        int maxx=mcss(0,n-1,l,r);
        printf("Case %d:\n",fir);
        printf("%d %d %d\n",maxx,l+1,r+1);
        if(fir!=T)puts("");
    }
    return 0;
}


累积遍历

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define maxn 10000+5
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IT iterator
#define push_back PB

typedef long long ll;
const double eps = 1e-9;
const double pi  = acos(-1);
const ll mod = 1e9+7; 
const int inf = 0x3f3f3f3f;

int main()
{
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    cin>>T;
    rep(cas,T)
    {
        int n,a;
        scanf("%d",&n);
        int maxx=-inf,sum=-inf,l,r,tl;
        rep(i,n)
        {
            scanf("%d",&a);
            if(sum<0)
            {
                sum=a;
                tl=i;
            }
            else sum+=a;
            if(sum>maxx)
            {
                maxx=sum;
                l=tl;
                r=i;
            }
        }
        printf("Case %d:\n",cas+1);
        printf("%d %d %d\n",maxx,l+1,r+1);
        if(cas!=T-1)puts("");
    }
    return 0;
}



动态规划

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define maxn 10000+5
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IT iterator
#define push_back PB

typedef long long ll;
const double eps = 1e-9;
const double pi  = acos(-1);
const ll mod = 1e9+7; 
const int inf = 0x3f3f3f3f;

int main()
{
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    cin>>T;
    rep(cas,T)
    {
        int n,maxx=-inf,sum=-inf,l,r,tl,a;
        scanf("%d",&n);
        rep(i,n)
        {
            scanf("%d",&a);
            if(sum+a>=a)sum+=a;
            else 
            {
                sum=a;
                tl=i;
            }
            if(sum>maxx)
            {
                maxx=sum;
                l=tl;
                r=i;
            }
        }
        printf("Case %d:\n",cas+1);
        printf("%d %d %d\n",maxx,l+1,r+1);
        if(cas!=T-1)puts("");
    }
    return 0;
}



第二种累积遍历

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define clr(x,y) memset(x,y,sizeof(x))
#define rep(i,n) for(int i=0;i<(n);i++)
#define repf(i,a,b) for(int i=(a);i<=(b);i++)
#define maxn 10000+5
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IT iterator
#define push_back PB

typedef long long ll;
const double eps = 1e-9;
const double pi  = acos(-1);
const ll mod = 1e9+7; 
const int inf = 0x3f3f3f3f;

int main()
{
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    cin>>T;
    rep(cas,T)
    {
        int n,a;
        scanf("%d",&n);
        int maxx=-inf,sum=0,minn=0,l,r,tl=0;
        rep(i,n)
        {
            scanf("%d",&a);
            sum+=a;
            if(sum-minn>maxx)
            {
                maxx=sum-minn;
                l=tl;
                r=i;
            }
            if(sum<minn)
            {
                minn=sum;
                tl=i+1;
            }
        }
        printf("Case %d:\n",cas+1);
        printf("%d %d %d\n",maxx,l+1,r+1);
        if(cas!=T-1)puts("");
    }
    return 0;
}


你可能感兴趣的:(算法,mcss)