【编程之美】读书笔记:求数组的子数组之和的最大值

来源:互联网 发布:淘宝宝贝发布运费模板 编辑:程序博客网 时间:2024/06/01 10:32
         问题:一个有N个整数元素的一维数组(A[0],A[1],A[2],...A[n-1]),这个数组中子数组之和的最大值是多少?

该子数组是连续的。例如 数组:[1,-2,3,5,-3,2]返回8; 数组:[0,-2,3,5,-1,2]返回9

        解法一:常规解法,时间复杂度为O(N^2)

       设Sum[i,...,j]为数组A中第i个元素到第j个元素的和(0<=i<=j<n),遍历所有的Sum[i,...,j],并且利用Sum[i,...,j]=Sum[i,....j-1]+A[j];

  int MaxSum(int *A,int n){int maximum=-INF;int sum;for(int i=0;i<n;i++){sum=0;for(int j=i;j<n;j++){sum+=A[j];if(sum>maximum)maximum=sum;}}return maximum;}

该算法的时间复杂度为O(N^2)

        解法二:采用二分策略(分治法),时间复杂度为(N*logN)。

       如果将所给数组(A[0],...,A[n-1])分为长度相等的两段数组(A[0],...,A[n/2-1])和(A[n/2],...,A[n-1]),分别求出这两段数组各自最大子段和,则原数组(A[0],...,A[n-1])的最大子段和分为以下三种情况:
          a.(A[0],...,A[n-1])的最大子段和与(A[0],...,A[n/2-1])的最大子段和相同;
          b.(A[0],...,A[n-1])的最大子段和与(A[n/2],...,A[n-1])的最大子段和相同;
          c.(A[0],...,A[n-1])的最大子段跨过其中间两个元素A[n/2-1]到A[n/2].
对应a和b两个问题是规模减半的两个相同的子问题,可以用递归求得。
对于c,需要找到以A[n/2-1]结尾的和最大的一段数组和S1=(A[i],...,A[n/2-1])和以A[n/2]开始和最大的一段和S2=(A[n/2],...,A[j]),那么第三种情况的最大值为S1+S2。
代码如下:
int MaxSum(const int A[],int Left,int Right)    {        int MaxLeftSum,MaxRightSum;              //左、右部分最大连续子序列值。对应情况a、b          int MaxLeftBorderSum,MaxRightBorderSum;  //从中间分别到左右两侧的最大连续子序列值,对应c。          int LeftBorderSum,RightBorderSum;        int Center,i;        if(Left == Right) //只有一个元素           if(A[Left]>0)                return A[Left];            else                return 0;            Center=(Left+Right)/2;            MaxLeftSum=MaxSum(A,Left,Center);            MaxRightSum=MaxSum(A,Center+1,Right);            MaxLeftBorderSum=0;            LeftBorderSum=0;            for(i=Center;i>=Left;i--)            {                LeftBorderSum+=A[i];                if(LeftBorderSum>MaxLeftBorderSum)                    MaxLeftBorderSum=LeftBorderSum;            }            MaxRightBorderSum=0;            RightBorderSum=0;            for(i=Center+1;i<=Right;i++)            {                RightBorderSum+=A[i];                if(RightBorderSum>MaxRightBorderSum)                    MaxRightBorderSum=RightBorderSum;            }            int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;            int max2=MaxLeftBorderSum+MaxRightBorderSum;            return max1>max2?max1:max2;    }    

         解法三:动态规划法(时间复杂度为O(N))

         将一个大问题(N个元素数组)转换为一个较小的问题(n-1个元素数组)。假设result[0]为已经找到数组[0,1,...n-1]中子数组和最大的,即保存当前找到的最大子数组。sum[i]为包含第i个元素且和最大的连续子数组。对于数组中的第i+1个元素有两种选择:
a.作为新子数组的第一个元素
b.放入前面已经找到的最大的子数组sum[i-1]中。
int max(int x,int y)  {  return (x>y)?x:y;  }int MaxSum(int * A,int n){ int *sum=(int *)malloc(n*sizeof(int)); int *result=(int *)malloc(n*sizeof(int));    sum[0]=A[0];  result[0]=A[0];  for(int i=1;i<n;i++){  sum[i]=max(A[i],A[i]+sum[i-1]);//若A[i]>A[i]+sum[i-1],则作为新子数组的第一个元素,否则放入前面已经找到的最大的子数组sum[i-1]中  result[i]=max(sum[i],result[i-1]);}return result[n-1];}

注:前面的算法额外申请了sum和result数组,其实在递推式
sum[i]=max(A[i],A[i]+sum[i-1]);
result[i]=max(sum[i],result[i-1]);中只需要两个变量就可以了。所以只需O(1)的空间。
[掌握下面这个写法]
int MaxSum(int * A,int n){  int sum=A[0];  int result=A[0];  for(int i=1;i<n;i++){  sum=max(A[i],A[i]+sum);  result=max(sum,result);}return result}

简洁的写法(掌握):
 int MaxSum(int* a, int n)    {        int sum=a[0];            int b=0;         for(int i=0; i<n; i++)        {            if(b<0)           ////                 b=a[i]; //小于0,重新计算起点           else                b+=a[i];   //大于0,就想加         if(sum<b)    //保存最大的和             sum=b;        }        return sum;    }    

原创粉丝点击