代码改变世界

[数字技巧]最大连续子序列和

2013-09-09 12:48  庸男勿扰  阅读(384)  评论(0编辑  收藏  举报

  最大连续子序列和这个问题是一个比较常见的问题,出现在很多公司的面试笔试中。题目大概是这样描述的:

  输入一个整形数组,数组中有正数也有负数,数组中连续一个或多个组成一个子序列,每个子序列都有一个和,求所有子序列的和的最大值。

  一、暴力枚举

  最暴力的解法是枚举起点和终点(或长度),连同求和的那个步骤,共三个for循环,复杂度是O(n3)。代码示例如下:

 1 #include <cstdio>
 2 #include <cstdlib>
 3 #include <limits.h>
 4 
 5 using namespace std;
 6 
 7 int n;
 8 int arr[1001];
 9 long maxRes;
10 int start;
11 int end;
12 long solve1(int *arr,int n)
13 {
14      long max = INT_MIN;
15      for(int i=0; i<n; i++){
16           for(int j=i; j<n; j++){
17                long total = 0;
18                for(int k=i; k<=j; k++)
19                     total += arr[k];
20                if(total>max)
21                     max = total;     
22           }        
23      }
24      return max;
25 }
26 int main()
27 {
28      freopen("maxSub.in","r",stdin);
29      freopen("maxSub.out","w",stdout);
30      
31      while(scanf("%d",&n)!=EOF && n>0){
32           for(int i=0; i<n; i++){
33                scanf("%d",&arr[i]);
34           }
35           maxRes = solve1(arr,n);
36           printf("%ld\n",maxRes);                          
37      }
38      return 0;    
39 }
View Code

  max表示从a[i]到a[j]的和。

  二、暴力枚举的改进  

  显然这样的算法是非常丑陋的,是否存在可优化的地方呢?一个最明显的冗余计算便是:arr[0]+arr[1]+...+arr[i]被计算了n-i-1次。实际上,第三个求和的过程是多余的,我们在枚举终点的过程中,即可进行求和运算,每加一个新数,即与最大值比较。这种做法可以省掉一个for循环,时间复杂度降到了O(n2),代码如下:

 1 int solve2(int *arr,int n)
 2 {
 3      long max = INT_MIN;
 4      for(int i=0; i<n; i++){
 5           long total = 0;
 6           for(int j=i; j<n; j++){               
 7                total += arr[j];
 8                if(total>max)
 9                     max = total;     
10           }        
11      }
12      return max;    
13 }
View Code

  三、巧妙的解法

  在做优化之前,我们应该有三个认识:

  1、以上的都枚举了起点和终点,而题目中只关心最大和,是不是可以优化。

  2、假设arr[i]是负数,那么最大子序列肯定不是以arr[i]为起点,因为以arr[i+1]为起点的子序列和肯定不会比以a[i]小。

  3、假设arr[i]...arr[j]的和是负数,那么最大子序列完全可以从arr[j+1]开始,原因同上。

  有了以上想法,我们不难写出以下代码:

 1 int solve3(int *arr,int n)
 2 {
 3      long max = 0;
 4      long total = 0;
 5      for(int i=0; i<n; i++){
 6           total += arr[i];
 7           if(total > max)
 8                max =  total;
 9           else if(total < 0)
10                total = 0;   
11      }
12      return max;    
13 }
View Code

  这是一个线性时间常量空间的算法,最大的特点是一旦读入arr[i]就被立即处理,不再被记忆,这就是所谓的联机算法。

  四、巧妙解法的两个改进

    改进一、这种算法在数组中所有的数为负数时,是得不到正确结果的,通过对负数单独讨论,满足全负数情形;

    改进二、在计算的过程中忽略了起始位置和终点位置,可通过做两次标记,保存起点和终点位置。

    这里直接给出一道类似的题目,具体不在赘述。

    http://www.cnblogs.com/codershell/p/3307795.html