给定k个整数的序列{N1,N2,...,Nk },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= k。最大连续子序列是所有连续子序中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{11,-4,13},最大连续子序列和即为20。
注:为方便起见,如果所有整数均为负数,则最大子序列和为第一个元素的值。
解决这样一个问题是一个很有趣的过程,我们可以尝试着从复杂度比较高的算法一步一步地推出复杂度较低的算法。
算法一:蛮力枚举
每个问题往往都有一个最直接而鲁莽的方法,虽然这样的方法不是我们最终想要的,但直接有效的方法能启发我们进一步优化。这里最直接的方法就是遍历所有的子数组,比较每一个子数组的和即得到最大的子数组和。 时间复杂度:O(N^3)
其代码:
#include <iostream> using namespace std; int maxSubArray(int *a,int n) { int maxSum=a[0]; int currSum=0; int i,j,k; for(i=0;i<n;i++) { for(j=i;j<n;j++) { for(k=i;k<=j;k++) { currSum+=a[k]; } if(currSum>maxSum) maxSum=currSum; currSum=0; } } return maxSum; } int main() { int a[]={1,-2,3,10,-4,7,2,-5}; int len=sizeof(a)/sizeof(int); int max=maxSubArray(a,len); cout<<max<<endl; return 0; }
算法二:
时间复杂度:O(N^2)
int maxSubArray1(int*a,int n) { int maxSum=a[0]; int currSum; for(int i=0;i<n;i++) { currSum=0; for(int j=i;j<n;j++) { currSum+=a[j]; if(currSum>maxSum) maxSum=currSum; } } return maxSum; }
对于这种方法,归根究底还是属于穷举法,其间接地求出了所有的连续子序列的和,然后取最大值即可。
那么,这里,我们需要对比一下前面两种算法,为什么同样都是穷举法,但算法一的时间复杂度远高于算法二的时间复杂度?
算法二相较于算法一,其优化主要体现在减少了很多重复的操作。
对于A-B-C-D这样一个序列,
算法一在计算连续子序列和的时候,其过程为:
A-B、A-C、A-D、B-C、B-D、C-D
而对于算法二,其过程为:
A-B、A-C、A-D、B-C、B-D、C-D
其过程貌似是一样的,但是算法一的复杂就在于没有充分利用前面已经求出的子序列和的值。
举个例子,算法一在求A-D连续子序列和的值时,其过程为A-D = A-B + B-C + C-D;
而对于算法二,A-D连续子序列和的求值过程为A-D = A-C+C-D;
这样,算法二充分利用了前面的计算值,这样就大大减少了计算子序列和的步骤。