最大子串和问题

  看别人的博客没弄懂,自己再将不太容易懂的地方理解了仔细表达出来。

  一个经典问题,对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。

这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为n的数组,其含有2^n个子序列和n(n+1)/2个子串。如果使用穷举法,则至少需要O(n^2)的时间才能得到答案。卡耐基梅隆大学的Jay Kadane的给出了一个线性时间算法,我们就来看看,如何在线性时间内解决最大子串和问题。

  要说明Kadane算法的正确性,需要两个结论。

  首先,对于array[1...n],如果array[i...j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i...k]的和大于0。因为如果存在k使得array[i...k]的和小于0,那么我们就有array[k+1...j]的和大于array[i...j],这与我们假设的array[i...j]就是array中和最大子串矛盾。

  其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p...q],是array的和最大子串,且array[p...q],跨越了array[i...j],array[j+1...k]。根据我们的分组方式,存在i<=m<j使得array[i...m]的和是array[i...j]中的最大值,存在j+1<=n<k使得array[j+1...n]的和是array[j+1...k]的最大值。由于array[m+1...j]使得array[i...j]的和小于0的,所以此时我们可以比较array[i...m]和array[j+1...n],如果array[i...m]的和大于array[j+1...n]则array[i...m]>array[p...q](因为此时如果跨两个子串选择,无论是array[m+1...j]+array[j+1...n]还是直接整个array[i...j]+array[j+1...n]都是小于a[i...m]的),否则array[j+1...n]>array[p...q]。无论谁大,我们都可以找到比array[p...q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p...q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。对于单元素和最大的特例,该结论也适用。

根据上述结论,我们就得到了Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。

下面是实现代码:

 

[cpp] view plaincopy
 
  1. int Kadane(const int array[], size_t length, unsigned int& left, unsigned int& right)  
  2. {  
  3.     unsigned int i, cur_left, cur_right;  
  4.     int cur_max, max;  
  5.   
  6.     cur_max = max = left = right = cur_left = cur_right = 0;  
  7.   
  8.     for(i = 0; i < length; ++i)  
  9.     {  
  10.         cur_max += array[i];  
  11.   
  12.         if(cur_max > 0)  
  13.         {  
  14.             cur_right = i;  
  15.   
  16.             if(max < cur_max)  
  17.             {  
  18.                 max = cur_max;  
  19.                 left = cur_left;  
  20.                 right = cur_right;  
  21.             }  
  22.         }  
  23.         else  
  24.         {  
  25.             cur_max = 0;  
  26.             cur_left = cur_right = i + 1;  
  27.         }  
  28.     }  
  29.   
  30.     return max;  
  31. }  


  这里我们需要注意,对于数组元素全为负的情况,由于不满足上述的两条结论,所以Kadane算法无法给出正确答案。但是如果题目或条件告诉了所有的数有上下限的时候,还是可以用Kadane方法求解。例如HD oj上的1003题目:告诉了数字的上下限为-1000到1000之间。那么这个时候就要换一种方式:先将最大和设为最小值-1000,不管第一个数是否为负,都一定比-1000大,所以就算全部的数字都为负,也能选出最大的负数。这个题目AC的代码如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 int main()
 5 {
 6     int i,j,group;
 7     j = 0;
 8     int a[100001];
 9     scanf("%d",&group);
10     if(group < 1 || group > 20)return -1;
11     while(j++ < group){
12         long num;
13         scanf("%ld",&num);
14         if(num < 1 || num >100000)return -1;
15         for(i=0 ;i<num ;i++)scanf("%d",&a[i]);
16         int cur_max,max,temp;
17         int left,right;
18         left = right = temp = 0;
19         max = -1000;
20         cur_max = 0;
21         for(i=0;i<num;i++)
22         {
23             cur_max += a[i];
24             if(cur_max > max)
25             {
26                 max = cur_max;
27                 left = temp;
28                 right = i;
29             }
30             if(cur_max < 0)
31             {
32                 cur_max = 0;
33                 temp = i+1;
34             }
35         }
36         printf("Case %d:\n%d %d %d\n",j,max,left+1,right+1);
37         if(j != group)printf("\n");
38     }
39     return 0;
40 }

 

该问题是1977年Ulf Grenander提出的一个数字图像方面的问题,1984年Jay Kadane才给出了这个优美的解决方案。有些问题,看似解法简单,但是实际上其原理,要比代码复杂得多。

posted @ 2014-09-03 21:54  Sprink  阅读(464)  评论(0编辑  收藏  举报