最大连续子序列和

来源:互联网 发布:java基本语法 编辑:程序博客网 时间:2024/06/05 02:53
最大连续子序列和
问题
对于形如:int arr[] = { 1, -5, 3, 8, -9, 6 };的数组,求出它的最大连续子序列和。
若数组中全部元素都是正数,则最大连续子序列和即是整个数组。
若数组中全部元素都是负数,则最大连续子序列和即是空序列,最大和就是0。

方法一
用sum[i,j]表示arr[i]到arr[j]的和,则显然可以通过枚举(i<=j),求出所有的和。
[cpp] view plain copy
  1. int maxSubArr(int *arr, int n)  
  2. {  
  3.     int i, j, k, sum, maxsofar;  
  4.     maxsofar = 0;  
  5.     for (i = 0; i < n; i++)  
  6.         for (j = i; j < n; j++)  
  7.         {  
  8.             sum = 0;  
  9.             for (k = i; k <= j; k++)  
  10.                 sum += arr[k];  
  11.             maxsofar = max(maxsofar, sum);  
  12.         }  
  13.     return maxsofar;  
  14. }  
时间复杂度O(n^3)

方法二-a
方法一中,有重复计算的嫌疑,显然sum[i,j]依赖于sum[i,j-1]:sum[i,j]=sum[i,j-1]+arr[j];所以不必每次都arr[i]+arr[i+1]……arr[j]才可得到sum[i,j],只需利用sum[i,j]和sum[i,j-1]的关系即可较少运算。这就是:对过程的解进行记录,以防以后用到,从而减少重复求解。
[cpp] view plain copy
  1. int maxSubArr(int *arr, int n)  
  2. {  
  3.     int i, j, k, sum, maxsofar;  
  4.     maxsofar = 0;  
  5.     for (i = 0; i < n; i++)  
  6.     {  
  7.         sum = 0;  
  8.         for (j = i; j < n; j++)  
  9.         {  
  10.             sum += arr[j];  
  11.             maxsofar = max(maxsofar, sum);  
  12.         }  
  13.     }  
  14.     return maxsofar;  
  15. }  
时间复杂度O(n^2)


方法二-b
对方法一的另一种优化是:先记录sum[0,j](j=0,1,……,n-1),而sum[i,j]=sum[0,j]-sum[0,i-1];这同样可以减少一层循环。
[cpp] view plain copy
  1. int maxSubArr(int *arr, int n)  
  2. {  
  3.     int i, j, k, sum, maxsofar;  
  4.     maxsofar = 0;  
  5.     int *sumcur = malloc(sizeof(int)*n);  
  6.     sumcur[0] = arr[0];  
  7.     for (i = 1; i < n; i++)  
  8.         sumcur[i] += arr[i];  
  9.     for (i = 0; i < n; i++)  
  10.     {  
  11.         for (j = i; j < n; j++)  
  12.         {  
  13.             sum = sumcur[j] - sumcur[i] + arr[i];    //[i, j]段的和  
  14.             maxsofar = max(maxsofar, sum);  
  15.         }  
  16.     }  
  17.     free(sumcur);  
  18.     return maxsofar;  
  19. }  
时间复杂度依然是O(n^2)。
为防止越界,使用sum[i,j]=sum[0,j]-sum[0,i]+arr[i];代替sum[i,j]=sum[0,j]-sum[0,i-1];

方法三
运用动态规划的思想,先把整个序列等分成两部分:arr[l,m]、arr[m+1,u](l->lower下界,u->upper上界)。
于是,最大和子序列只可能出现在三处:
  1. arr[l,m]
  2. arr[m+1,u]
  3. 最大和子序列跨越arr[m]
对于1,2是相同的子问题,可递归求解。对于3可使用简单的算法直接求解。
[cpp] view plain copy
  1. int maxsum3(int *arr, int l, int u)  
  2. {  
  3.     if (l > u) return 0;   //zero elements  
  4.     if (l == u) return max(0, arr[l]);   //one element  
  5.     int i, j;  
  6.     int m = (l + u) / 2;  
  7.     int lmax, rmax, sum;  
  8.     lmax = rmax = 0;  
  9.     sum = 0;  
  10.     for (i = m; i >= l; i--)  
  11.     {  
  12.         sum += arr[i];  
  13.         lmax = max(sum, lmax);  
  14.     }  
  15.     sum = 0;  
  16.     for (i = m + 1; i <= u; i++)  
  17.     {  
  18.         sum += arr[i];  
  19.         rmax = max(sum, rmax);  
  20.     }  
  21.     return max(lmax + rmax, maxsum3(arr, l, m), maxsum3(arr, m + 1, u));  
  22. }  
  23. int maxSubArr(int *arr, int n)  
  24. {  
  25.     return maxsum3(arr, 0, n - 1);  
  26. }  
可以证明它的时间复杂度是O(nlogn)


方法四
该算法只需遍历一次arr,即可得出结果。
逻辑是:
arr[0,i](i=0,1,...,n-1)的连续子序列有很多,特别地考虑其中一种:以arr[i]结尾的子序列。使用maxendinghere表示这种特殊子序列的最大和。我们考虑最大和子序列的组成特点,有且仅有两种:
  1. 与arr[0,i-1]相同。
  2. 以arr[i]结尾。
maxsofar记录的是arr[0,i]的最大和子序列。显然,每遍历一个元素,就得更新maxsofar
[cpp] view plain copy
  1. int maxSubArr(int *arr, int n)  
  2. {  
  3.     int maxendinghere, maxsofar;  
  4.     maxendinghere = maxsofar = 0;  
  5.     int i;  
  6.     for (i = 0; i < n; i++)  
  7.     {  
  8.         maxendinghere = max(0, maxendinghere + arr[i]);  
  9.         maxsofar = max(maxsofar, maxendinghere);  
  10.     }  
  11.     return maxsofar;  
  12. }  
时间复杂度O(n)。


深入思考:如何确定最大和子序列的构成呢?也就是确定arr[i,j]中的i,j。
原创粉丝点击