编程之美---求数组的子数组之和的最大值

问题描述:

一个有N个整数元素的一维数组(A[0],A[1],A[2],...A[n-1]),这个数组中子
数组之和的最大值是多少?该子数组是连续的。
例如 数组:[1,-2,3,5,-3,2]返回8; 数组:[0,-2,3,5,-1,2]返回9

问题求解:

#include <iostream>
#include <malloc.h>
#include <limits.h>
using namespace std;

//方法1:O(n^2) 遍历算法
//设Sum[i,...,j]为数组a中第i个元素到第j个元素的和(0<=i<=j<n)
//遍历所有的Sum[i,...,j],并且利用Sum[i,...,j]=Sum[i,....j-1]+a[j];
int MaxSum1(int *a,int n)
{//宏INT_MIN定义类型为int的最小值,包含在头文件#include <limits.h>中
    int maximum = INT_MIN;
    int sum;
    for(int i=0;i<n;i++)
    {
        sum=0;
        for(int j=i;j<n;j++)
        {
            sum+=a[j];
            if(sum>maximum)
                maximum=sum;
        }
    }
    return maximum;
}

//方法2:O(nlogn) 分治算法
//如果将所给数组(A[0],...,A[n-1])分为长度相等的两段数组
//(A[0],...,A[n/2-1])和(A[n/2],...,A[n-1]),分别求出这
//两段数组各自最大子段和,则原数组(A[0],...,A[n-1])的最大子段和分
//为以下三种情况:a.(A[0],...,A[n-1])的最大子段和与
//(A[0],...,A[n/2-1])的最大子段和相同;
//b.(A[0],...,A[n-1])的最大子段和与(A[n/2],...,A[n-1])
//的最大子段和相同;c.(A[0],...,A[n-1])的最大子段跨过其中间两个
//元素A[n/2-1]到A[n/2].对应a和b两个问题是规模减半的两个相同的子
//问题,可以用递归求得。对于c,需要找到以A[n/2-1]结尾的和最大的一段
//数组和S1=(A[i],...,A[n/2-1])和以A[n/2]开始和最大的一段和S2=
//(A[n/2],...,A[j]),那么第三种情况的最大值为S1+S2。
int MaxSum2(int *a, int low, int high)
{
    if(low >= high)
        return a[low];
    int mid = (low+high)>>1;
    int MaxLeftSum=MaxSum2(a,low,mid);//情况a:左半段最大子段和
    int MaxRightSum=MaxSum2(a,mid+1,high);//情况b:右半段最大子段和
    //情况c第一部分:MaxSumLeftBorder为以a[mid]结尾的最大一段数组之和
    int SumLeftBorder = 0, MaxSumLeftBorder = INT_MIN;
    for(int i=mid;i>=low;i--)
    {
        SumLeftBorder += a[i];
        if(SumLeftBorder > MaxSumLeftBorder)
        {
            MaxSumLeftBorder = SumLeftBorder;
        }
    }
    //情况c第二部分:MaxSumRightBorder为以a[mid + 1]
    //开始的最大一段数组之和
    int SumRightBorder = 0, MaxSumRightBorder = INT_MIN;
    for(int i=mid+1;i<=high;i++)
    {
        SumRightBorder += a[i];
        if(SumRightBorder > MaxSumRightBorder)
        {
            MaxSumRightBorder = SumRightBorder;
        }
    }

    int Maxab=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;
    //情况c:最大子段跨过中间两元素a[n/2-1]、a[n/2]的最大子段和
    int Maxc=MaxSumLeftBorder + MaxSumRightBorder;
    return Maxab>Maxc?Maxab:Maxc;
}

//方法3:动态规划法(时间复杂度为O(N))
//将一个大问题(N个元素数组)转换为一个较小的问题(n-1个元素数组)。
//假设result[0]为已经找到数组[0,1,...n-1]中子数组和最大的,
//即保存当前找到的最大子数组。
//sum[i]为包含第i个元素且和最大的连续子数组。对于数组中的第i+1个元素
//有两种选择:a.作为新子数组的第一个元素
//b.放入前面已经找到的最大的子数组sum[i-1]中。
int MaxSum3(int *a,int n)
{
    int *sum=(int *)malloc(n*sizeof(int));
    int *result=(int *)malloc(n*sizeof(int));
    //sum[i]为包含第i个元素且和最大的连续子数组
    //result[i]保存当前找到的最大子数组
    sum[0]=a[0];
    result[0]=a[0];
    for(int i=1;i<n;i++)
    {//若a[i]>a[i]+sum[i-1],则作为新子数组的第一个元素,否则放
    //入前面已经找到的最大的子数组sum[i-1]中
        sum[i]=max(a[i],a[i]+sum[i-1]);
        result[i]=max(sum[i],result[i-1]);
    }
    return result[n-1];
}

//方法4:对方法3的改进,只需O(1)的空间复杂度
int MaxSum4(int a[],int n)
{
    int sum=a[0];
    int result=a[0];
    for(int i=1;i<n;i++)
    {
        sum=max(a[i],a[i]+sum);
        result=max(sum,result);
    }
    return result;
}

//方法4的变形!!!!!!!!!!!
int MaxSum44(int a[],int n)
{
    int sum=a[n-1];
    int result=a[n-1];
    for(int i=n-2;i>=0;i--)
    {
        //sum < 0时,会重置sum的值,这时相当于是一个新的子数组的起点
        if(sum<0)
            sum=0;
        sum += a[i];
        //sum > result时,更新result的值,
        if(sum > result)
            result=sum;
    }
    return result;
}

//扩展:返回最大子数组的位置
//在sum < 0时,会重置sum的值,相当于这时是一个新的子数组的起点
//在sum > result时,更新result的值,这时我们更新最大子数组的位置
int MaxSum5(int a[], int n, int *start, int *end)
{
    //sum :以当前元素开始的和最大的一段子数组
    //result:存储当前所有子数组的最大值
    //CurStart:存储以当前元素开始的和最大的一段子数组的开始位置
    int sum=a[n-1];
    int result=a[n-1];

    *start=n-1;
    *end=n-1;
    int CurStart=n-1;
    //从数组末尾往前遍历,直到数组首
    for(int i=n-2;i>=0;i--)
    {
        if(sum < 0)
        {
            sum=0;
            //重新标记最大和的起始位置!!!
            CurStart=i;
        }
        sum += a[i];
        if(sum > result)
        {
            result=sum;
            //更新最大和的范围!!!
            *start=i;
            *end=CurStart;//从后往前遍历的原因
        }
    }
    return result;
}
int main()
{
    int a1[6]={1,-2,3,5,-3,2};
    int a2[6]={0,-2,3,5,-1,2};
    int a3[5]={-9,-2,-3,-5,-3};
    cout<<MaxSum1(a1,6)<<endl;
    cout<<MaxSum1(a2,6)<<endl;
    cout<<MaxSum1(a3,5)<<endl;
    cout<<MaxSum2(a1,0,5)<<endl;
    cout<<MaxSum2(a2,0,5)<<endl;
    cout<<MaxSum2(a3,0,4)<<endl;
    cout<<MaxSum3(a1,6)<<endl;
    cout<<MaxSum3(a2,6)<<endl;
    cout<<MaxSum3(a3,5)<<endl;
    cout<<MaxSum4(a1,6)<<endl;
    cout<<MaxSum4(a2,6)<<endl;
    cout<<MaxSum4(a3,5)<<endl;
    cout<<MaxSum44(a1,6)<<endl;
    cout<<MaxSum44(a2,6)<<endl;
    cout<<MaxSum44(a3,5)<<endl;
    cout<<"**********返回最大子数组的位置**********"<<endl;
    int start=0,end=0;
    cout<<MaxSum5(a1,6,&start,&end)<<endl;
    cout<<"start: "<<start<<" end: "<<end<<endl;
    cout<<MaxSum5(a2,6,&start,&end)<<endl;
    cout<<"start: "<<start<<" end: "<<end<<endl;
    cout<<MaxSum5(a3,5,&start,&end)<<endl;
    cout<<"start: "<<start<<" end: "<<end<<endl;
    return 0;
}

代码执行结果:

8
9
-2
8
9
-2
8
9
-2
8
9
-2
8
9
-2
**********返回最大子数组的位置**********
8
start: 2 end: 3
9
start: 2 end: 5
-2
start: 1 end: 1

扩展问题:要求返回最大子数组和的位置,如何处理?
见上述代码及分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值