最大子段和问题

来源:互联网 发布:西南交大希望学院网络 编辑:程序博客网 时间:2024/05/21 06:29

                                                           最大子段和问题

参考博客:博客链接

一.问题描述

N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。

例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13,和为20。
二.问题分析
这是一个经典动规问题,有以下几种解题方法。
1.穷举法
int start = 0;//起始位置int end = 0;  //结束位置int max = 0;for(int i = 1; i <= n; i++){    for(int j = i; j <= n; j++)    {        int sum = 0;        for(int k = i; k <=j; k++)        {            sum += a[k];        }        if(sum > max)        {            start = i;            end = j;            max = sum;        }    }}

这个算法的时间复杂度是O(n^3)。当然,这个代码还可以优化,实际上我们并不需要每次都重新从起始位置求和加到终点位置.可以充分利用之前的计算结果.

或者我们换一种穷举思路,对于起点 i,我们遍历所有长度为1,2,…,n-i+1的子区间和,以求得和最大的一个.这样也遍历了所有的起点的不同长度的子区间,同时,对于相同起点的不同长度的子区间,可以利用前面的计算结果来计算后面的.

比如,i为起点长度为2的子区间和就等于长度为1的子区间的和+a[i+1]即可,这样就省掉了一个循环,计算时间复杂度减少到了O(n^2).代码如下:

int start = 0;//起始位置int end = 0;//结束位置int max = 0;for(int i = 1; i <= n; ++i){    int sum = 0;    for(int j = i; j <= n; ++j)    {        sum += a[j];        if(sum > max)        {            start = i;            end = j;            max = sum;        }    }}
2.分治法

分治算法一般分为如下三个步骤:

(1)划分问题:把问题的实例划分成子问题

(2)递归求解:递归解决子问题

(3)合并问题:合并子问题的解得到原问题的解

在本例中,“划分”就是把序列分成元素个数尽量相同的两半;“递归求解”就是分别求出完全位于左半或完全位于右半的最佳序列;“合并”就是求出起点位于左半、终点位于右半的最大连续和序列,并和子问题的最优解比较。

前两部分没有什么特别之处,关键在于“合并”步骤。既然起点位于左半,终点位于右半,则可以人为地把这样的序列分成两部分,然后独立求解:先寻找最佳起点,然后再寻找最佳终点。

求子区间及最大和,从结构上是非常适合分治法的,因为所有子区间[start, end]只可能有以下三种可能性:

  • 在[1, n/2]这个区域内
  • 在[n/2+1, n]这个区域内
  • 起点位于[1,n/2],终点位于[n/2+1,n]内

以上三种情形的最大者,即为所求. 前两种情形符合子问题递归特性,所以递归可以求出. 对于第三种情形,则需要单独处理. 第三种情形必然包括了n/2和n/2+1两个位置,这样就可以利用第二种穷举的思路求出:

  • 以n/2为终点,往左移动扩张,求出和最大的一个left_max
  • 以n/2+1为起点,往右移动扩张,求出和最大的一个right_max
  • left_max+right_max是第三种情况可能的最大值
代码:
int maxsum(int *A,int x,int y)//返回数组在左闭右开[x,y)中的最大连续和{    int v,L,R,maxs;    if(y-x==1)return A[x];//只有一个元素直接返回    int m=x+(y-x)/2;//分治第一步:划分成[x,m),[m,y)    int maxs=max(maxsum(A,x,m),maxsum(A,m,y));//分治第二步:递归求解    int v,L,R;    v=0;L=A[m-1];//分治第三步:合并(1)——从分界点开始往左的最大连续和    for(int i=m-1;i>=x;i--)    {        L=max(L,v+=A[i]);    }    v=0,R=A[m];//分治第三步:合并(2)——从分界点开始往右的最大连续和    for(int i=m;i<y;i++)    {        R=max(R,v+=A[i]);    }    return max(maxs,L+R);//把子问题的解与L和R比较}
3.动规

动态规划主要讨论这个问题的建模过程和子问题结构.时刻记住一个前提,这里是连续的区间

  • 令b[j]表示以位置 j 为终点的所有子区间中和最大的一个
  • 子问题:如j为终点的最大子区间包含了位置j-1,则以j-1为终点的最大子区间必然包括在其中
  • 如果b[j-1] >0, 那么显然b[j] = b[j-1] + a[j],用之前最大的一个加上a[j]即可,因为a[j]必须包含(以j为终点)
  • 如果b[j-1]<=0,那么b[j] = a[j] ,因为既然要最大,前面的负数必然不能使之更大
代码:

int maxx=0;for(int i=1; i<=n; i++){    if(dp[i-1]>0)    {        dp[i]=dp[i-1]+a[i];    }    else dp[i]=a[i];    maxx=max(maxx,dp[i]);}











原创粉丝点击