子序列最大和
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了子序列最大和相关的知识,希望对你有一定的参考价值。
问题描述:
x[i]中,从任意一个数x[a]到x[b](a<=b)的连续的序列叫做x[i]的子序列,
x[i]的子序列的和叫做x[i]的子序列的序列和。
现在已知序列x[i],求一些关于最大序列和的问题答案。
一、子序列最大和
【方法一】
朴素的查找,利用S[i]=S[0]+…+S[i]
进行一次O(n^2)的查找,并比较S[i]-S[j](i>j)
求得最大值,即为答案
【代码】
略
【适用范围】
需要直观地了解最大序列和的起止位置
【方法二】见补充【需要求序列位置】【分治】
【方法三】【不可以求序列位置】【动态规划】
【数据结构】
int lmax[MAXN]
【代码】
lmax[0] = x[0];
for (int i=1; i<n; i++)
if (lmax[i-1]<=0)
lmax[i]=x[i];
else
lmax[i]=lmax[i-1] + x[i];
/*
* 在已知lmax[i-1]为包含x[i-1]的最大子序列和的条件下,
* 如果lmax[i-1]为负数,则x[i]必大于lmax[i-1],故lmax[i]=x[i]
* 如果lmax[i-1]为非负数,则x[i]+lmax[i-1]必大于x[i]或其他到x[i]为止的序列,故lmax[i]=x[i]+lmax[i-1]
*/
二、双子序列最大和
“双”这个字很奇特,见到“双”,那肯定是有奇技淫术可以使用。
注意,这里的双,意思是两个子序列之间至少有1个单位长度的间隔。
那么我们很自然地想到了这样一个场景:
没错,普通的算法肯定能写,但复杂度一定不低,
所以如果想要降低复杂度,程序的突破口就是在上图的缺口处。
设缺口长度为1且位置为k
令图中左边的序列为S_left=∑▒x[i] (i=1..k-1)
令右边的序列为S_right=∑▒〖x[i](i=k+1..n)S
S_left 中的最大子序列和与S_right 中的最大子序列和的和 就是双子序列最大和
现在所要解决的问题是,引入一对新的量,lf[i]与lr[i],分别表示从最左、最右到i的子序列中的最大序列和。
在得知lmax(与rmax)的基础上,我们可以很轻松地获取lf与lr,只需扫描一遍即可。
出于节约空间的考虑,我们继续用lmax和rmax来表示lf和lr
【代码】
2
3 for (int i=1; i<n; i++)
4
5 if (lmax[i-1]<=0)
6
7 lmax[i]=x[i];
8
9 else
10
11 lmax[i]=lmax[i-1] + x[i];
12
13 /*此部分同上*/
14
15 for (int i=1; i<n; i++)
16
17 if (lmax[i]<lmax[i-1])
18
19 lmax[i]=lmax[i-1];
20
21 /*右边同理*/
22
23 rmax[n-1]=rmax[n-2];
24
25 for (int i=n-1; i>=0; i--)
26
27 if (rmax[i+1]<=0)
28
29 rmax[i]=x[i];
30
31 else
32
33 rmax[i]=rmax[i+1] + x[i];
34
35 for (int i=n-1; i>=0; i--)
36
37 if (rmax[i]<rmax[i+1])
38
39 rmax[i]=rmax[i+1];
40
41
补充
对这个问题,有一个相对复杂的O(NlogN)的解法,就是使用递归。如果要是求出序列的位置的话,这将是最好的算法了(因为我们后面还会有个O(N)的算法,但是不能求出最大子序列的位置)。该方法我们采用“分治策略”(divide-and-conquer)。
在我们例子中,最大子序列可能在三个地方出现,或者在左半部,或者在右半部,或者跨越输入数据的中部而占据左右两部分。前两种情况递归求解,第三种情况的最大和可以通过求出前半部分最大和(包含前半部分最后一个元素)以及后半部分最大和(包含后半部分的第一个元素)相加而得到。
//递归法,复杂度是O(nlogn)
long maxSumRec(const vector<int>& a, int left, int right)
{
if (left == right)
{
if (a[left] > 0)
return a[left];
else
return 0;
}
int center = (left + right) / 2;
long maxLeftSum = maxSumRec(a, left, center);
long maxRightSum = maxSumRec(a, center+1, right);
//求出以左边对后一个数字结尾的序列最大值
long maxLeftBorderSum = 0, leftBorderSum = 0;
for (int i = center; i >= left; i--)
{
leftBorderSum += a[i];
if (leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
}
//求出以右边对后一个数字结尾的序列最大值
long maxRightBorderSum = 0, rightBorderSum = 0;
for (int j = center+1; j <= right; j++)
{
rightBorderSum += a[j];
if (rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return max3(maxLeftSum, maxRightSum,
maxLeftBorderSum + maxRightBorderSum);
}
long maxSubSum3(const vector<int>& a)
{
return maxSumRec(a, 0, a.size()-1);
}
另外max3(long,long,long)表示求三个long中的最大值:
//求出三个long中的最大值
long max3(long a, long b, long c)
{
if (a < b)
{
a = b;
}
if (a > c)
return a;
else
return c;
}
对这个算法进行分析:
T(1) = 1
T(N) = 2T(N/2) + O(N)
最后得出算法的复杂度为:O(NlogN) 。
补充部分 来自 <http://www.cnblogs.com/CCBB/archive/2009/04/25/1443455.html>
以上是关于子序列最大和的主要内容,如果未能解决你的问题,请参考以下文章