最大连续子序列和

本文探讨了最大连续子序列和问题的多种算法解决方案,包括三层循环的暴力方法、预生成序列和差分求解法、分治法以及高效算法。详细分析了每种方法的时间复杂度和实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:最大连续子序列和

问题描述: 给出一个长度为 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;
      }
   }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值