【经典问题】最大子串和

最近几天好好的研究了一下这个问题。

问题本身就不多说了,求一串数字中的所有子串中,和最大的一个子串。例如:

  输入:-10 5 2 3 4 -5 -23 3 7 -21

  输出:14 5 4

一、各种方法

    方法1:

maxsofar = 0
for i = [0,n)
    for j = [i,n)
        sum = 0
        for k =[i,j]
            sum += x[k]
        maxsofar = max(maxsofar,sum)

  这是最直接最暴力的方法,我没有写,时间复杂度为O(n3),明显这里面有很多重复的运算,我们可以很容易的把时间复杂度降为O(n2)。实际上,我第一印象就是下面这个

  方法2:

int s,e;
int maxsum = -999999;
int tempsum = 0;
for(int i=0;i<K;i++)
{
    tempsum = 0;
    for(int j=i;j<K;j++)
    {
        tempsum+=input[j];
        if(maxsum < tempsum)
        {
              maxsum = tempsum;
              s = i;
              e = j;
        }
    }
}    

  这个方法理解为,以x[i]开头的所有子串中,和最大的一个。比枚举所有的 i 和 j 减少了计算量。下面一个比较重要的方法,虽然在时间复杂度上面没有提高,却包含了一个应对区间问题很重要的技巧。

  方法3:

cumarr[-1] = 0
for i = [0,n)
    cumarr[i] = cumarr[i-1] + x[i]
maxsofar = 0
for i = [0,n)
    for j = [i,n)
        sum = cumarr[j] - cumarr[i-1]
        maxsofar = max(maxsofar,sum)

  这个算法中,比较重要的一点事注意到子串和x[i...j] = cumarr[j] = cumarr[i-1],这个经验可以用在区间问题上。一个简单的列子:

    n个收费站之间有n-1段路,每段路花费为P,用O(1)时间求任意两个收费站的之间的花费,要求空间为O(n)。

  万能的二分能不能用到这个问题上,显然是可以的。在这个二分的过程中,需要注意的就是,合并结果的时候,需要注意到,跨左边跟右边的子串和的计算,最后就是在左边的最大子串、右边的最大子串、中间跨界的最大串这三者中取最大值。

  方法4:

 1 int max_sub(int m,int n)
 2 {
 3     if(m > n)
 4         return 0;
 5     if(m == n)
 6         return max(0,input[m]);
 7    
 8     int k = (m+n)/2;
 9     //中间部分的结果显然是由以x[k]结尾的最大子串和
10     //和以x[k+1]开头的最大子串和  相加而得
11     int lmax,sum,a;
12     lmax = sum = a = 0;
13     for(int i=k;i>=m;i--)
14     {
15         sum += input[i];
16         if(lmax < sum)
17         {
18              lmax = sum;
19              a = i;
20         }
21     }
22     int rmax,b;
23     rmax = sum = b = 0;
24     for(int i=k+1;i<=n;i++)
25     {
26         sum += input[i];
27         if(rmax < sum)
28         {
29             rmax = sum;
30             b = i;
31         }
32     }
33     int max_l = max_sub(m,k);
34     int max_r = max_sub(k+1,n);
35     int result = max(lmax+rmax,max(max_l,max_r));
36     /*
37         how to record the start and the end
38     */
39     return result;
40 }

  对于这个二分,还有一个待解决的问题,我想尝试一下,记录最大子串的起始位置和结束位置。个人对这种递归的理解确实不够,还没能够实现记录起始和结束位置。看来我还是得抽空好好把递归这个玩意儿好好理解一下。二分的话,显然时间复杂度为O(n*logn)。

     接下来,就是O(n)的方法了,再来回顾一下方法3中,sum = cumarr[j] - cumarr[i-1]。要使得sum的值最大,显然cumarr[j]越大,cumarr[i-1]的值越小,sum的值越大。于是我们可以遍历cumarr数组,维持一个最小的cumarr[min_s],然后取cumarr[j]-cumarr[min_s]的最大值。

  方法5:

 1 min_s = -1//这里要初始化为-1
 2 cumarr[-1] = 0;
 3 for(int i=0;i<K;i++)
 4     cumarr[i] = cumarr[i-1] + input[i];
 5 for(int i=0;i<K;i++)
 6 {
 7     sum = cumarr[i] - cumarr[min_s];
 8     if(maxsofar < sum)
 9     {
10          maxsofar = sum;
11          s = min_s+1;
12          e = i;
13      }
14       if(cumarr[min_s] > cumarr[i])
15             min_s = i;
16 }

  这里还有一个方法6:

 1 int start = 0;
 2 for(int i=0;i<K;i++)
 3 {
 4     if(maxendinghere+input[i]>0)
 5     {
 6          maxendinghere += input[i];
 7     }
 8     else if(maxendinghere + input[i] <= 0)
 9     {
10          maxendinghere = 0;
11          start = i + 1;
12     }
13     if(maxendinghere > maxsofar)
14     {
15          maxsofar = maxendinghere;
16          e = i;
17          s = start;
18     }
19 }

  对于这个方法我要转载一个比较好的解释:

    -10   1  2  3  4  -5   -23   3  7    -21   (num)

    -10 | 1  3  6 10  8 | -23 |  3 10 | -21   (Sum)(|号为该处Sum被清零)

       由于10是Sum的最大值,所以,红色的那块就是要求的范围了。

  这样就比较好理解第六种方法。

二、思考问题:

  1.证明最大子串和的时间复杂度下届是O(n)

    各位如果有思路或者资料麻烦告诉我一声。。。无处下手啊

  2.求子串和最接近0的子串。

    嗯,对于这个问题,前面的经验有 sum = cumarr[j]-cumarr[i-1],这样的话问题就转换成,求cumarr数组里面差值最小或者相等的两个元素。

            用排序的方法,再遍历一次数组就可以得出结果,时间复杂度为O(n*logn)。 有没有更好的方法?

  3.求子串和最接近t的子串。

    这个问题,如果继续采用问题二的方案,问题转换成,在一个排好序的数组里面找两个值的差值为t,显然不能达到同样的效果。暂时没有想到其他更好的方法。

  4.m和n为整数,给定x[n],求整数i,使得 x[i] + ... + x[i+m] 的和最接近为0。(即在第二个问题的基础上,加了条件: m个元素)

    这个问题因为相差为m,扫一遍cumarr数组貌似就ok了。

 min = max_num
 cumarr[-1] = 0
 for i = [0,n)
     if(i+m<n)
         temp = cumarr[i+m]-cumarr[i-1]
     if (min > temp)
         min = temp
         start = i
         end = i+m

 

 

 

 

  想要透彻的理解一个算法真的很难,我感觉我还是不太会思考。上面大部分内容是参考编程珠玑上面的内容,看懂正文好像不是太难,但是后面的习题就各种傻了。也许是我看得太少的缘故吧。

  接下来想要看得主题 column 4 writing correct programs

参考资料:

  《programming Pearls》

    http://blog.csdn.net/hcbbt/article/details/10454947

posted @ 2013-10-11 10:26  细胞核  阅读(1528)  评论(4编辑  收藏  举报