【程序员编程艺术】第七章:最大连续子数组和

来源:互联网 发布:淘宝美工兼职怎么样 编辑:程序博客网 时间:2024/04/28 02:29


题目:要求一个数组连续下标和的最大值,数组的元素可正、可负、可为零,例如-2,5,3,-6,4,-8,6将返回8。


拿到一个题目,先尝试着用通用方法去做,然后再考虑如何用学过的算法(分治法,动态规划,贪心算法等等)去优化。如果能力达到一定程度,还是总结一下算法的分类比较好。行了,先不说很多,下面讲方法。


这里先介绍三种不同的解法:

1:暴力枚举法

2:分治法

3:动态规划


一:暴力枚举法

这也是最容易想出来的方法:我要求解最大的,那么尝试各种组合,这就涉及到概率论的东西:从左向右依次遍历,每次遍历时依次加上一个数据;整个过程要记录下最大的数。也就是Cn2种组合。时间复杂度为O(n^2)。

代码如下:

int max_sum_array1(int arr[],int *left,int *right,int n){    int maxarr=0,sum=0;    int i,j;    for(i=0;i<n;i++){        sum = 0;        for(j=i;j<n;j++){            sum += arr[j];            if(maxarr < sum){   //时刻准备更新最大值!                maxarr = sum;                *left = i;                *right = j;            }        }    }    return maxarr;}

二:分治法

也就是递归求解。时间复杂度为O(nlogn)。这是个分治的思想,解决复杂问题我们经常使用的一种思维方法——分而治之。
而对于此题,我们把数组A[1..n]分成两个相等大小的块:A[1..n/2]和A[n/2+1..n],最大的子数组只可能出现在三种情况:
    A[1..n]的最大子数组和A[1..n/2]最大子数组相同;
    A[1..n]的最大子数组和A[n/2+1..n]最大子数组相同;
    A[1..n]的最大子数组跨过A[1..n/2]和A[n/2+1..n]
前两种情况的求法和整体的求法是一样的,因此递归求得。
第三种,我们可以采取的方法也比较简单,沿着第n/2向左搜索,直到左边界,找到最大的和maxleft,以及沿着第n/2+1向右搜索找到最大和maxright,那么总的最大和就是maxleft+maxright。因为要跨过中间点,因此返回必须为maxleft和maxright之和!
而数组A的最大子数组和就是这三种情况中最大的一个。

代码如下:

int max_sum_array2(int arr[],int low,int high,int *left,int *right){    if(low == high){    //递归结束点        *left = low;        *right = high;        return arr[low];    }    int mid = (low+high)/2;    int lleft,lright,rleft,rright,mleft,mright;    //三种情况下分别求解。    int lmax = max_sum_array2(arr,low,mid,&lleft,&lright);    int rmax = max_sum_array2(arr,mid+1,high,&rleft,&rright);    int mmax = find_max_crossing_subarray(arr,low,mid,high,&mleft,&mright);    //返回最大值    if(lmax > rmax && lmax > mmax){        *left = lleft;        *right = lright;        return lmax;    }else if(rmax > lmax && rmax > mmax){        *left = rleft;        *right = rright;        return rmax;    }else{        *left = mleft;        *right = mright;        return mmax;    }}int find_max_crossing_subarray(int arr[],int low,int mid,int high,int *left,int *right){    int lsum = -100;    int sum = 0;    for(int i=mid;i>=low;i--){  //这里的算法类似一趟暴力算法,求出最大值        sum += arr[i];        if(sum > lsum){            lsum = sum;            *left = i;        }    }    int rsum = -100;    sum = 0;    for(int j=mid+1;j<=high;j++){        sum += arr[j];        if(sum > rsum){            rsum = sum;            *right = j;        }    }    return lsum+rsum;}


三:动态规划

时间复杂度为O(n)。由于有上面的递归算法,就可以尝试着用动态规划的方法去求解。

直接说解法吧,具体的思想理论,有待重新整理:

设sum[i]为以第i个元素结尾且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

代码如下:

int maxsum(int a[n])    //于此处,你能看到上述思路2代码(指针)的优势  {      int max=a[0];       //全负情况,返回最大数      int sum=0;      for(int j=0;j<n;j++)      {          if(sum>=0)     //如果加上某个元素,sum>=0的话,就加              sum+=a[j];          else                 sum=a[j];  //如果加上某个元素,sum<0了,就不加          if(sum>max)              max=sum;      }      return max;  } 




问题扩展

如果数组是二维数组,同样要你求最大子数组的和列?
如果是要你求子数组的最大乘积列?
如果同时要求输出子段的开始和结束列?

举一反三
1 给定整型数组,其中每个元素表示木板的高度,木板的宽度都相同,求这些木板拼出的最大矩形的面积。并分析时间复杂度。
此题类似leetcode里面关于连通器的题,需要明确的是高度可能为0,长度最长的矩形并不一定是最大矩形,还需要考虑高度很高,但长度较短的矩形。如[5,4,3,2,4,5,0,7,8,4,6]中最大矩形的高度是[7,8,4,6]组成的矩形,面积为16。

2、环面上的最大子矩形
《算法竞赛入门经典》 P89 页。

3、最大子矩阵和
一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的值。如果所有数都是负数,就输出0。 例如:3*5的矩阵:
1 2 0 3 4
2 3 4 5 1
1 1 5 3 0
和最大的子矩阵是:
4 5
5 3
最后输出和的最大值17。

4、允许交换两个数的位置 求最大子数组和。








参考:三种算法求解一个数组的子数组最大和

0 0
原创粉丝点击