问题:最大连续子序列和
问题描述: 给出一个长度为 n 的序列 A1,A1,···,An,求最大连续子序列和。换句话说, 要求找到 1<= i <= j <= n, 使得 Ai + Ai+1+····+Aj 尽量大。
方案1、
按正常逻辑进行处理,求出每一个元素开头的所有子序列和,找到其中最大的那个。
代码:
best = A[1];
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 > best ) best = sum;
}
三层循环的最坏取值都是 n ,所以复杂度是 O(n^3) .
方案2、
若 Sn = A1 + A2 + ···· + An。 则子序列 Ai + Ai+1 + ··· + Aj = Sj - Si-1
s[0] = 0;
for( int i = 1; i <= n; i++ )
s[i] = s[i-1] + A[i];
for( int i = 1; i <= n; i++ )
for( int j = i; j <= n; j++ )
best = max( best, s[j] - s[i-1] );
从层序片段看, 首先从一个单循环预生成了 A1 开头的所有连续子序列的和。 其次,通过两个循环 计算 Sj - Si-1, 即 Ai+Ai+1 + …… + Aj 的值。求其中最大者。
算法复杂度由第二个双层循环决定, 所以复杂度是 O(n^2);
方案3:
分治法, 这个题也可以通过分治法来求解。
分治法描述:
划分问题: 把问题的实例划分成子问题。
递归求解: 递归解决子问题。
合并问题: 合并子问题的解得到原问题的解。
“划分” 就是把序列尽量切成元素个数相同的两部分; “递归求解” 就是指分别求出两部分的最佳序列; “ 合并” 就是求出起点位于左半,终点位于右半的最大连续和序列,并和子序列中的和值做比较。
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;
maxs = max( maxsum( A, x, m ), maxsum( A, m, y ) ) // 区间[x,m), [m, y)
v = 0; L = A[m-1];
for( int i = m-1; i>= x; i-- ) L= max( L, v += A[i] );
v = 0; R = A[m];
for( int i = m; i < y; i++ ) R = max( R, v += A[i] );
return max( maxs, L + R );
}
从分析上来说,每一次都切分一半, 需要切分 log(n) 次,在每次切分完后要执行 [x,y) 次的合并操作,最坏取值为 n 。所以
复杂度可以表示为 O(nlog(n))。
方案4:
其实这个题目还有个更高效的算法,复杂度为 O(n) .
维护 Si 最小,则 Sj - Si 即可最大。
void maxsum( int* A, int n ){
S[0] = 0;
for( int i = 1; i <= n; i++ ){
S[i] = S[i-1] + A[i];
mins = min( mins, S[i] );
maxs = max( maxs, s[i] );
}
}
另一种考虑是,在计算的过程进行处理,但想法基本和上面的想法一致。
即 在计算 Si-1 + A[i] 的时候, 如果 Si < 0 , 则 肯定有 Sj < Sj - Si, 所以 max = Sj - Si .
void maxsum( int* A, int n ){
int maxs = A[1], S = 0;
for( int i = 1; i <= n; i++ ){
maxs = max( maxs, s += A[i] );
if( s < 0 ) s = 0;
}
}
在这里利用的动态规划的思想, 即定义前一个状态 s 是前面子序列和的最大值,与当前 最大值进行比较,取最大值。如果 状态 s 的值为负值,则当前以后元素的状态和 p
的值 总是会大于 s + p 的值。故 让 s = 0; 而 前计算 状态 s 的过程已经记录下其中所能达到的最大值,故并不会影响到最终的结果。
如果要计算元素的其实位置, 则增加两个变量, 在 maxs 出现改变的时候更改终点位置, 在 s 变 0 的时候更改起点和终点。
例如:
void maxsum( int* A, int n ){
int maxs = A[1], S = 0;
int b, e, mb, me;
b = e = mb = me = 1;
for( int i = 1; i <= n; i++ ){
//maxs = max( maxs, s += A[i] ); // 有可能后面 有 maxs == s 出现
if( s += A[i] > maxs ){
maxs = s;
e = i;
}
if( s < 0 ){
s = 0;
if( e != me ){ mb = b; me = e; }
b = e = i;
}
}
}