• 数组的子数组之和的最大值


            求子数组之和的最大值是一个很经典的问题。问题的描述如下:一个有N个整形数的一维数组(A[0], A[1], ... A[n-1]),这个数组有很多子数组,那么子数组之和的最大值是什么呢?

             这个问题的解答其实在《编程珠玑》一书有的。一共是4中方法:第一种是穷举法,计算所有可能子数组的和,时间复杂度为O(n3)。

     

    第二种其实也是穷举法。代码如下:

    [cpp] view plaincopy

    1. for(i = 0;i < n;i++)  
    2. {  
    3.  sum = 0;  
    4.  for(j = i;j < n;j++)  
    5.  {  
    6.   sum += A[j];  
    7.   if(sum > maxsum)  
    8.    maxsum = sum;  
    9.  }  

    10. }  


     

             很明显复杂度为O(n2)。第三种方法是分治法,将数组元素均分成两部分,那么最大子数组和只有三种情况。在左边部分,右边部分,以及跨越了边界部分。这种方法是时间复杂度为O(nlogn)。不是最优的就不列代码了。

    第四种是最优的,时间复杂度为O(n),利用了动态规划的思想。具体代码如下:

     

    A[0]是首元素,

    start[1]是包含a[1]的和最大的数组,

    all[1]是可能包含也可能不包含a[1]的和最大的数组,

    所以a[0]…..a[n-1]的解all[0]是max{a[0],   a[0]+start[1],  all[1]};

     

    1. int MaxSubSum1(int *A,int n)  
    2. {  
    3.     int start[n-1], all[n-1];  
    4.     all[n-1]= A[n-1];
    5.      start[n-1]=A[n-1];  
    6.     for(int i = n-2; i >= 0; i--)  
    7.     {  
    8.         start[i] = Max(A[i], start[i+1] + A[i]);  
    9.         all[i] = Max(all[i+1], start[i]);  
    10. 10.     }  
    11. 11.     return all[0];  

    12. }  

    这个代码有额外申请了两个数组,下面是改进的算法,避免开辟数组空间

     

     

    分治算法的模型

     

    [cpp] view plaincopy

    13. int MaxSubSum1(int *A,int n)  

    14. {  

    1. 15.     int start, all;  
    2. 16.     all = start = A[n-1];  
    3. 17.     for(int i = n-2; i >= 0; i--)  
    4. 18.     {  
    5. 19.         start = Max(A[i], start + A[i]);  
    6. 20.         all = Max(all, start);  
    7. 21.     }  
    8. 22.     return all;  

    23. }  

    还可以进行下一步的改进;

    24. int MaxSubSum1(int *A,int n)  

    25. {  

    1. 26.     int start, all;  
    2. 27.     all = start = A[n-1];  
    3. 28.     for(int i = n-2; i >= 0; i--)  
    4. 29.     {  
    5. 30.        if(start<0)
    6. 31.             start=0;
    7. 32.        start+=a[i];
    8. 33.        if(start>all)
    9. 34.            all=start;
    10. 35.         
    11. 36.  
    12. 37.     }  
    13. 38.     return all;  


     

            这种方法不论是空间和时间都已是最优的了,在《编程之美》中列举了改进的过程,最终的程序就是上面的这段代码。

            如果是二维数组呢,又当如何解答。《编程之美》中给出的解法是穷举矩形区域的所有可能的上下边界,再用一维的方法计算该上下边界的最大和。时间复杂度为 O(n2*m)。当然也可以先穷举矩形区域的所有可能的左右边界。本质上是一样的。下面给出了一种解法。这里用了一个辅助数组B,B[ i ][ j ]表示第 j 列元素的前 i  行元素的和。B[ i + 1 ][ j ]=A[ 0 ][ j ]+A[ 1 ][ j ]+...A[ i ][ j ]。B[ 0 ][ j ]做为哨兵,全部为0。

    [cpp] view plaincopy

    1. int MaxSubSum2(int *A, int n, int m)    
    2. {    
    3.     int i, j, k;    
    4.     //初始化辅助数组    
    5.     int **B = new int*[n+1];    
    6.     for(i = 0; i <= n; i++)    
    7.         B[i] = new int[m];    
    8.     for(j = 0; j < m; j++)  //第0行做为哨兵    
    9.         B[0][j] = 0;    
    10. 10.     for(i = 0; i < n; i++)    
    11. 11.         for(j = 0; j < m; j++)    
    12. 12.             B[i+1][j] = B[i][j]+A[i*n+j];    
    13. 13.     //开始计算    
    14. 14.     int maxsum = 0x80000000;  //设为最小值    
    15. 15.     for(i = 1; i <= n; i++)    
    16. 16.     {    
    17. 17.         for(j = i; j <= n; j++)    
    18. 18.         {    
    19. 19.             int start, all;    
    20. 20.             start = all = (B[j][m-1]-B[i-1][m-1]);    
    21. 21.             for(k = m-2; k >= 0; k--)    
    22. 22.             {    
    23. 23.                 start = Max(B[j][k]-B[i-1][k], start + B[j][k]-B[i-1][k]);     
    24. 24.                 all = Max(all, start);    
    25. 25.             }    
    26. 26.             if(all > maxsum)    
    27. 27.                 maxsum = all;    
    28. 28.         }    
    29. 29.     }    
    30. 30.     for(i = 0; i <= n; i++) //释放辅助空间  
    31. 31.         delete [] B[i];  
    32. 32.     delete B;  
    33. 33.     return maxsum;    

    34. }  

    问题描述:

    一个有N个整数元素的一维数组(A[0],A[1],...A(n-1),它包含很多子数组,求子数组之和的最大值,当数组元素全部为负的时候,有两种处理办法,第一种是返回0,第二种是返回数组中最大的负数。

    解法1:

    使用暴力法,假设最大的一段数组为A[i],...,A[j],则对i:=0~n-1 j:=i~n-1,遍历一遍,求出最大的Sum(i,j)即可

    解法2:

    使用分治法,数组(A[0],A[1],...A(n-1)分为长度相等的两段数组(A[0],...,A[n/2])以及(A[n/2+1],...,A[n-1]),分别求出这两段数组各自的最大子段和,则原数组(A[0],A[1],...A(n-1)的最大子段和分为3种情况
    1)(A[0],A[1],...A(n-1)的最大子段和与(A[0],...,A[n/2])的相同
    2)(A[0],A[1],...A(n-1)的最大子段和与(A[n/2+1],...,A[n-1])的相同
    3)(A[0],A[1],...A(n-1)的最大子段和跨过(A[0],...,A[n/2])与(A[n/2+1],...,A[n-1])

    解法3:
    使用动态规划法,假设A[0],A[1],...A(n-1)的最大子段为A[i],...,A[j],则有以下3种情况
    1)当0=i=j的时候,元素A[0]本身构成和最大的一段
    2)当0=i<j的时候,和最大的一段以A[0]开头
    3)当0<i时候,元素A[0]跟和最大的一段没有关系
    则原始问题A[0],A[1],...A(n-1)的解All[0]=max{A[0],A[0]+Start[1],ALL[1]}

     

    程序说明:

    下面的程序中,针对数组全部为负的情况的处理(处理1:直接返回0,处理2:返回数组中最大的负数)定义了两种情况,分别对应于定义了RETURN_ZERO和RETURN_MAXMINUS这两种情况

     

        /*********************问题描述******************

    一个有N个整数元素的一维数组(A[0],A[1],...A(n-1),
        它包含很多子数组,求子数组之和的最大值,当数组元
        素全部为负的时候,有两种处理办法,第一种是返回0,
        第二种是返回数组中最大的负数
        ************************************************/  
        #include<iostream>  
        using namespace std;  
          
          
        //定义了RETURN_ZERO说明处理全部为负数的数组时候返回0  
        #define RETURN_ZERO  
        #ifdef RETURN_ZERO  
        /*******************************解法一:蛮力法*****************************
        依次计算A[0],A[0..1],...A[0..n-1],A[1],A[1..2]...A[1..n-1],A[2],A[2..3],...
        A[2..n-1]...并返回最大的值maximum
        ****************************************************************************/  
        int MaxSum1(int *A,int length){  
            int maximum=0;          //子数组和最大值  
            int sum;                //子数组和  
            for(int i=0;i<length;i++){  
                sum=0;  
                for(int j=i;j<length;j++){  
                    sum+=A[j];  
                    if(sum>maximum)  
                        maximum=sum;  
                }  
            }  
            return maximum;  
        }  
        /*******************************解法二:分治法*******************************
        数组(A[0],A[1],...A(n-1)分为长度相等的两段数组(A[0],...,A[n/2])以及(A[n/2+1],
        ...,A[n-1]),分别求出这两段数组各自的最大子段和,则原数组(A[0],A[1],...A(n-1)
        的最大子段和分为3种情况:
        1)(A[0],A[1],...A(n-1)的最大子段和与(A[0],...,A[n/2])的相同
        2)(A[0],A[1],...A(n-1)的最大子段和与(A[n/2+1],...,A[n-1])的相同
        3)(A[0],A[1],...A(n-1)的最大子段和跨过(A[0],...,A[n/2])与(A[n/2+1],...,A[n-1])
        *****************************************************************************/  
        int MaxSum2(int *A,int left,int right){  
            if(left==right){  
                return max(A[left],0);  
            }  
            int middle=(left+right)/2;  
            //求(A[0],...A[n/2-1])中子数组包含A[n/2-1]的最大值  
            int lmaximum=0;  
            int lsum=0;  
            for(int i=middle;i>=left;i--){  
                lsum+=A[i];  
                if(lsum>lmaximum)  
                    lmaximum=lsum;  
            }  
            //求(A[n/2],...,A[n-1])中子数组包含A[n/2]的最大值  
            int rmaximum=0;  
            int rsum=0;  
            for(int i=middle+1;i<=right;i++){  
                rsum+=A[i];  
                if(rsum>rmaximum)  
                    rmaximum=rsum;  
            }  
            return max(lmaximum+rmaximum,max(MaxSum2(A,left,middle),MaxSum2(A,middle+1,right)));  
        }  
        /******************************解法三:动态规划**********************************
        假设A[0],A[1],...A(n-1)的最大子段为A[i],...,A[j],则有以下3种情况,
        1)当0=i=j的时候,元素A[0]本身构成和最大的一段
        2)当0=i<j的时候,和最大的一段以A[0]开头
        3)当0<i时候,元素A[0]跟和最大的一段没有关系
        则原始问题A[0],A[1],...A(n-1)的解All[0]=max{A[0],A[0]+Start[1],ALL[1]}
        *********************************************************************************/  
        //从尾到首动态规划(编程之美2.14的思想)  
        int MaxSum3(int *A,int length){  
            int nStart=0;  
            int nAll=0;  
            for(int i=length-1;i>=0;i--){  
                nStart=max(0,A[i]+nStart);  
                nAll=max(nStart,nAll);  
            }  
            return nAll;  
        }  
        //从首到尾动态规划(编程珠玑8.4思想)  
        int MaxSum3_v2(int *A,int length){  
            int nStart=0;  
            int nAll=0;  
            for(int i=0;i<length;i++){  
                nStart=max(0,A[i]+nStart);  
                nAll=max(nStart,nAll);  
            }  
            return nAll;  
        }  
        #endif 

    这个问题源于《编程之美》2.14 求数组的子数组之和的最大值扩展问题2

    输出子数组的最大和同时输出子数组下标,时间复杂度为O(N)
    源码:

    #include <iostream>
    using namespace  std;
    //startPos 最大和子数组的起始位置 
    //endPos 最大和子数组的结束位置  
    int MaxSum(int *A,int n,int &startPos,int &endPos)
    {
        int tmpStart = 0;
        int nStart = A[0];
        int nAll = A[0];
        for (int i=1;i<n;i++)
         {
            if(nStart < 0)
             {
                nStart =0;
                startPos = i;
            }
            nStart += A[i];
            if (nStart>nAll)
             {
                nAll = nStart;
                endPos =i;
                tmpStart = startPos;
            }
        }
        startPos = tmpStart;
        return nAll;
    }
    int main()
    {
        int arr[11] =  {-2,5,6,-6,4,-8,6,3,-1,2,-9};
        int startP = 0,endP = 0;
        int maxsubArrSumValue =0;
        maxsubArrSumValue = MaxSum(arr,11,startP,endP);
        cout<<maxsubArrSumValue<<endl<<startP+1<<endl<<endP+1<<endl;

        system("pause");
        return 0;
    }

    二维的情况

    最直接的方法就是枚举

    int max(int x,int y)

    {

    return (x>y) ? x:y;

    }

    int MaxSum(int * A,int n,int m)

    {

    maxinum=-INF;

    for(i_min=1;i_min<=n;i_min++)

        for(i_max=i_min;i_max<=n;i_max++)

             for(j_min;j_min<=m;j_min++)

                  for(j_max=j_min;j_max<=m;j_max++)

                 maximum=max(maximum,Sum(A,i_min,i_max,j_min,j_max));

    return maximum;

    }

     二维中,假设已经确定了矩形区域的上下边界,比如知道矩形区域的上下边界分别是第a行和第c行,现在要确定左右边界。 
     
    转化为一个一维问题,可以把每一列中第a行和第c行之间的元素看成一个整体。即求数组(BC[1],...BC[M])中和最大的一段,其中B[i] = B[a][i]+...B[c][i]. 

    为了优化程序,我们可以先做一些预处理,并把计算结果保留下来

    定义PS[i][j] 为以(1,1),(i,1),(1,j),(i,j)为边界的矩形区域元素之和

    所以任意区域之和可以表示成

    PS[i_max][j_max]-PS[i_min-1][j_max]-PS[i_max][j_min-1]+PS[i_min-1][j_min-1];

    for(int i=0;i<=n;i++)

    PS[i][0]=0;

    for(int j=0;j<=M;j++)

    PS[0][j]=0;

    for(int i=1;i<=n;i++)

          for(int j=1;j<=M;j++)

               PS[i][j]=PS[i-1][j]+PS[i][j-1]+PS[i-1][j]-PS[i-1][j-1]+B[i][j];

    另外的一种解法就是将二维的问题转换为一维的问题:

    假设我们已经确定了矩形的上下边界分别是a,c那么现在需要确定左右边界,问题转换成求一维情况下的最大和

    int MaxSum(int * A,int n,int m)

    {

    maximum=-INF;

    for(int a=1;a<=n;a++)

         for(int c=a;c<=n;c++)

         {

             Start=BC(a,c,m);

             ALL=BC(a,c,m);

             for(int i=m-1;i>=2;i--)

                 {

                  if(Start<0)

                  Start=0;

                  Start+=BC(a,c,i);

                  if(Start > ALL)

                  All=Start;

                 }

                  if(All > maximum)

                   maximum=ALL;

         }

    return maximum;

    }

  • 相关阅读:
    postgresql创建表
    PG查询数据库大小
    unicode字符集范围
    删除mysql数据中的空格和换行符
    无法生成模型:“System.Data.StrongTypingException: 表“TableDetails”中列“IsPrimaryKey”的值为 DBNull
    ubuntu 16.04 64位 coreseek
    linux wps 缺失字体问题解决
    误删除/var/lib/dpkg解决办法
    Microsoft Office CVE-2017-8570
    mariadb ==> 开机自启动
  • 原文地址:https://www.cnblogs.com/fickleness/p/3154996.html
Copyright © 2020-2023  润新知