zoukankan      html  css  js  c++  java
  • 【经典问题】最大子串和

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

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

      输入:-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

  • 相关阅读:
    MDK常用快捷键
    Visual C++ 6.0常用快捷键
    STM32内存映射
    STM32固件库
    MDK建立STM32F103*开发模板
    STM32下载方法
    Protel DXP画原理图常见错误与警告
    usb host和usb device
    IAR使用记录
    开发新产品的三个验证阶段(EVT/DVT/PVT)
  • 原文地址:https://www.cnblogs.com/xibaohe/p/3361697.html
Copyright © 2011-2022 走看看