最大子数列和的问题求解

来源:互联网 发布:java 写成绩划分 编辑:程序博客网 时间:2024/05/20 08:01

问题描述:求解连续子数列和最大。即给定一个序列a1,a2,…,an
求得ai,…,aj之和最大。ai,…,aj是连续的子数列。

数组a[0..n-1]
首先看最容易想到的:从下标i = 0,到下标 i = n-1
子数列就将从:
0 ~ n-1
1 ~ n-1
2 ~ n-1

n-1 ~ n-1

问题变成对这些序列的最大和的求解问题。
就是说从这些子数列中选择子数列,不是一定以最后一个元素作为结尾的子数列。这个写法可能会引起误导,特别说明。

设问题是从i~n-1,那么定义sum = 0, 并定义max = a[0];
循环从i到n-1,如果加上当前数字大于max,那么就更新max值否则不更新。

这样最终得到的最大的max便是要求的结果。
这是最简单但是复杂度很高的思路。

int maxsequence(vector<int> arr, int n) //O(n^2)算法{    int max = arr[0]; //初始化最大值为第一个元素    for(int i = 0; i < n; i++)    {        int sum = 0;        for(int j = i; j < n; j++)        {            sum += arr[j];            if(sum > max) //如果加到一个正数也不一定可以更新max,因为前面可能加了好几个负数            {                max = sum;            }        }    }    return max;}

这个双层循环,因此复杂度是O(n2).
这个算法读起来会容易产生错误的感觉,实际上可以这样理解,比如i = 0时,子序列从第一个元素开始,但是从哪里结束是不定的。注意到sum是从0到后面的每一个元素的加和。如果sum值大于max了,将max值更新。特别注意sum是在连续加和。一轮循环结束表示从0开始的子序列最大的max值是什么。

sum = a[0]
sum = a[0]+a[1]
sum = a[0]+a[1]+a[2]
sum = a[0]+a[1]+a[2]+a[3]

sum = a[0]+a[1]+a[2]+a[3]+…+a[n-1]

每一次加和都与当前max比较,看是否可以更新max.
那么这一轮结束,就知道从0开始的子数列和最大是什么了。

再计算从1开始的子序列最大可能达到的max值。
再计算从2开始…

从n-1开始到n-1的max值。

注意这里的max是同一个变量,因此max在动态变化。

最好的算法是O(n),而且实现这个算法也很简单,因此,大多使用下面这个算法。

int maxsequence(vector<int> a, int n){    int maxSum, maxCur;    maxSum = maxCur = a[0];   //初始化最大和为a[0]    for (int i=1; i<n; i++) {        if (maxCur <= 0)//这里其实是maxCur+a[i]<= a[i]化简的版本        {             maxCur = a[i];  //如果前面位置最大连续子序列和小于等于0,则以当前位置i结尾的最大连续子序列和为a[i]        }        else        {            maxCur += a[i]; //如果前面位置最大连续子序列和大于0,则以当前位置i结尾的最大连续子序列和为它们两者之和        }        if (maxCur > maxSum)         {            maxSum = maxCur;  //更新最大连续子序列和        }    }    return maxSum;}

这其实是思考问题的另外一个角度:用增量的思路来看。
假设这个最大子数列从第一个元素开始,那么什么时候我们认为不可能是从第一个元素开始了呢?
首先,第一个元素是负数,第一感觉就告诉你,哦,这个不会是最大子数列开头。只有在中间的负数,你才会想,后面可能出现的正数,填补了负数带来的亏空。
OK,现在第一个数不是负数 ,那么就有资格做一下领头试试看。往后加,出现正数,当然大欢喜,继续走,如果是负数,心里紧张一下,算一算和如果变负数或者是0了,那么表示前面的都白费了,啥也没累积到,于是换个起点重新出发。
再用同样的思路去跟踪。
比如在加到a[i]时和为0或者是个负数了,马上断腕,重新设定起点为a[i],再重新求和,一直往后推进。每次更新完当前的最大值,就要和全局的最大值比较,看是不是可以更新它。

就是这样的过程,但是这个分析可能有错,下面更新。

2016.11.15 00:28 update: 重新思考后发现以前的认知是不完善的,首先一样,我们假设第一个数是起始最大值与当前最大值。从第二个元素往后面扫描过去。如果当前最大值maxCur加上a[i]后,变得比a[i]小了,即:maxCur + a[i] <= a[i]。
这个我们也分成两部分看:

  • maxCur + a[i] < a[i]
  • maxCur + a[i] = a[i]

第一种表示,前面的累计和没什么乱用,加上当前值居然比当前值小,那么正常情况下,你会做怎样的决策?好比说,一条路上连续分布着一些物品,要求你看到物品都必须捡起来,有些物品是有价值的,有些物品价值是负的,具体怎么个负法自由想象。当你看到第i件物品时,你必须得捡起来啊,所以捡起来加和计算一下当前的和,如果背包里所有的价值和比第i件物品价值小,你忍不住会想,哎?干嘛不把包里的东西全都扔了!只留第i件物品呢?另外,由这个式子我们知道,等同于说maxCur是个负值。而第i件物品呢,可以是正可以是负,如果是负的价值,则下一轮验证时会更新。

第二种,如果加上眼前的这个物品价值等于眼前的物品价值,当然可以选择两种策略,不管直接往后加,或者把其他的全部扔了,只要眼前这件物品。价值是一样的。其实也就是说maxCur = 0。

综上两种情况,在算法中常常写的是maxCur <= 0,其实不是直接反映问题的实质,是做了一次数学计算后的结果。

理解了这个关键点,整个算法的思路是非常简单易懂的。

此外,这个算法还可以标记子数组的起点,也就是更新maxCur = a[i]时,也可以更新起点就是i,用个容器全局存一下即可。而在更新max时,用一个全局容器存储当前的i也就是目前状况下的最大子数列的右端下标。继续扫描更新即可。

0 0
原创粉丝点击