子序列最大和

 

问题描述:

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

【代码】

 1 lmax[0] = x[0];
 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  
View Code


 

补充

对这个问题,有一个相对复杂的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>

posted on 2016-07-02 15:51  Chuckqgz  阅读(...)  评论(...编辑  收藏

导航