最大连续子序列问题的研究

最大连续子序列,即从一个序列中选取一段连续的子序列,使这个子序列的和最大,例如0 6 -1 1 -6 7 -5 最大和为7,从第一个数到第6个数,最大子序列有动态规划法和分治法两种,首先讨论分治法,对一个序列而言,最大连续子序列要么在左边的序列中,要么在右边的序列中(从中间断开),要么在左右各一部分,显然,从这个角度想,我们将问题划分为了两个子问题,从左边找最大连续子序列或者从右边找连续子序列,如果在中间,那么我们只需要暴力遍历,左边从mid到left,右边从mid+1到right,

显然,T(n)=T(n/2)*2+n,其中n代表最坏情况下最大子序列在中间,而且从left到right,复杂度为O(nlog2n)以下是我实现的代码。


<span style="font-family:Courier New;">int *maxsum(int a[],int left,int right) {
    int *m=new int[3]; //m[0]记录最大连续子序列起始位置,m[1]记录终止位置,m[2]表示最大和
    if(left==right) { // 返回条件
        m[0]=m[1]=left;
        m[2]=a[left]; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
        return m;
    }
    int mid=(left+right)/2;
    int *l=maxsum(a,left,mid); 
    int *r=maxsum(a,mid+1,right); //左右递归,划分子问题
    int leftsum=a[mid],maxleft=a[mid]; //暴力求解maxleft
    m[0]=mid;
    for(int i=mid-1;i>=left;i--) {
        leftsum+=a[i];
        if(leftsum>=maxleft) {
            maxleft=leftsum; m[0]=i;
        }
    }

    int rightsum=a[mid+1],maxright=a[mid+1];</span><span style="font-family: 'Courier New';">//暴力求解maxright</span><span style="font-family:Courier New;">

    m[1]=mid+1;
    for(int i=mid+2;i<=right;i++) {
        rightsum+=a[i];
        if(rightsum>=maxright) {
            maxright=rightsum; m[1]=i;
        }
    }
    m[2]=maxleft+maxright;
    if(l[2]>=m[2] && l[2]>=r[2]) { //防止内存泄露,及时处理new的内存
        delete m; delete r; //cout<<l[0]<<" "<<l[1]<<" "<<l[2]<<endl;
        return l;
    }
    else if(m[2]>=l[2] && m[2]>=r[2]) {
        delete l; delete r; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
        return m;
    }
    else if(r[2]>=m[2] && r[2]>=l[2]) {
        delete m; delete l; //cout<<r[0]<<" "<<r[1]<<" "<<r[2]<<endl;
        return r;
    }
}</span>

以下是杭电1003的分治法求解实现,使用上述函数


<span style="font-family:Courier New;">#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=100000+5;
int t[maxn];

int *maxsum(int a[],int left,int right) {
    int *m=new int[3];
    if(left==right) {
        m[0]=m[1]=left;
        m[2]=a[left]; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
        return m;
    }
    int mid=(left+right)/2;
    int *l=maxsum(a,left,mid);
    int *r=maxsum(a,mid+1,right);
    int leftsum=a[mid],maxleft=a[mid];
    m[0]=mid;
    for(int i=mid-1;i>=left;i--) {
        leftsum+=a[i];
        if(leftsum>=maxleft) {
            maxleft=leftsum; m[0]=i;
        }
    }

    int rightsum=a[mid+1],maxright=a[mid+1];
    m[1]=mid+1;
    for(int i=mid+2;i<=right;i++) {
        rightsum+=a[i];
        if(rightsum>=maxright) {
            maxright=rightsum; m[1]=i;
        }
    }
    m[2]=maxleft+maxright;
    if(l[2]>=m[2] && l[2]>=r[2]) {
        delete m; delete r; //cout<<l[0]<<" "<<l[1]<<" "<<l[2]<<endl;
        return l;
    }
    else if(m[2]>=l[2] && m[2]>=r[2]) {
        delete l; delete r; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
        return m;
    }
    else if(r[2]>=m[2] && r[2]>=l[2]) {
        delete m; delete l; //cout<<r[0]<<" "<<r[1]<<" "<<r[2]<<endl;
        return r;
    }
}
int main() {
   int T;
   cin>>T;
   for(int ca=1;ca<=T;ca++ ){
        int n;
        cin>>n;
        for(int i=0;i<n;i++) {
            cin>>t[i];
        }
        int *p=maxsum(t,0,n-1);
        cout<<"Case "<<ca<<":"<<endl;
        cout<<p[2]<<" "<<p[0]+1<<" "<<p[1]+1<<endl;
        delete p;
        if(ca<T) cout<<endl;
   }
   return 0;
}</span>

2,尝试使用动态规划法求解,对我而言真的很不显然,以下标 i 结尾的序列的最大连续字段和 maxsum[i] = ( maxsum[i-1] , 0) +a[i] (其实就是判断前n-1个最大字段和是否大于0 ,如果大于,显然加上前面的,否则,要自己就够了) 显然复杂度为O(n),根据maxsum的值,暴力向前,暴力向后,也能找到起始和结束,以下是实现的代码,还是上面的一题hdu1003


<span style="font-family:Courier New;">#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;

const int maxLen=100000+5;
int a[maxLen];
int maxn[maxLen];
int main()
{
    int T;
    scanf("%d",&T);
    for(int time=1;time<=T;time++)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        maxn[0]=-1005;
        for(int i=1;i<=n;i++)
        {
            maxn[i]=max(a[i],a[i]+maxn[i-1]);
        }
        int maxNum=-1005,End=0;
        for(int i=1;i<=n;i++)
        {
            if(maxn[i]>maxNum)
            {
                maxNum=maxn[i];End=i;
            }
        }
        printf("Case %d:\n%d",time,maxNum);
        int Begin;
        int sum=0;
        for(int i=End;i>=1;i--)
        {
            sum+=a[i];
            if(sum==maxNum) Begin=i;
        }
        printf(" %d %d\n",Begin,End);
        if(time<T)
            printf("\n");
    }
    return 0;
}</span>



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值