最大连续子数组和与最大连续子矩阵和

来源:互联网 发布:羽毛球鞋 推荐 知乎 编辑:程序博客网 时间:2024/06/16 20:33

这两个问题是编程中常见的问题,而且网上有大量博客论述,这里主要是自己做一个笔记。两个之间是有关系的,所以这次放在一起复习


  1. 最大连续子数组

先看第一个问题:

给定一个整数数组,数组里面可能有正数,负数、零。数组中的一个或多个连续数组构成一个子数组,每个子数组都有一个和,求所有子数组和的最大值。


例子:数组a[]={1,-2,3,10,-4,7,2,5}.那么和最大的子数组为{3,10,-4,7,2},所以结果输出18.

求解:

思路一:暴力法
找出数组的所有子数组,然后求组所有子数组中和最大的,其中确定一个子数组需要起始位置和终止位置,所以这一步的复杂度是O(n2),但是还要讲数组中的元素加起来,所以最后的复杂度是O(n3).
思路二:动态规划方法
假设给你一个数组A[n]={a1,a2,,an},然后我告诉你前n1sn1,
然后告诉你an的大小,你能否告诉我A的最大连续子数组和?答案是不行的,即使最后一个数是正数,我们也不能认为最大和为Sn1+an,因为我们不知道前n-1个数的最大子数组包不包括an1,如果包括那么可以相加,如果不包括无法相加。举个例子
A[]={1,2,3,10,20}前四个数的最大子数组是1,2,3,所以和为6,虽然20是正数我们不能直接加上去,因为-10不在里面,加上20可能不连续。其实A的最大子数组就是20,所以需要一个条件:an1结束的最大连续子数组的和,假设这个和为Cn1,那么知道an,就可以知道:

Cn=max{0,Cn1}+an

那么最大子数组的和就是:
Sn=max{Cn,Sn1}
,这里Sn1n1
还是上面的例子,C4=1+2+310=4,
S4=1+2+3=6,
C5=max{0,4}+a5=20,
s5=max{C5,S4}=20,

所以程序中需要两个变量,一个是以ai结尾的最大连续子数组的和currSum(i),一个是去全局最大子数组的和maxSum。初始化均可设置为0,currSum的更新规则为:

currSum(i+1)=max(0,surrSum(i)+ai+1)

函数代码如下:

int maxSubArray(int a[],int n){    int maxSum=0;    int currSum=0;    for(int i=0;i<n;i++)    {        if(currSum>=0)            currSum+=a[i];//更新currSum        else            currSum=a[i];        if(currSum>maxSum)            maxSum=currSum;//更新maxSum    }    return maxSum;}

整个过程中currSum和maxSum更新如下:

currSum:0113139161813

maxSum:01131313161818

拓展

  1. 如果要求求出最大连续子数组的和,并且同属输出所求子数组的开始位置和起始位置?
    分析:这个问题还是比较好解决的,只要清楚什么最大子数组开始的条件和结束的条件就可以了,可以设置几个flag,其中开始点应该是currSum由负数变正数,且currSum大于maxSum的时候。而结束的时候应该是currSum大于maxSum且由正数变为正数。
  2. 如果要求出最大子数组的和,但不要求子数组是连续的呢?
    分析:这个好办,大于0的数相加即可。
  3. 如果是二维数组,同样要求求出最大连续子数组的和呢?

针对第三个问题,引申出下面第二个问题:

给定一个M×N的矩阵,找出此矩阵的一个子矩阵,要求满足这个子矩阵的元素和是最大的,输出这个最大值,如果所有数是负数,就输出0.

例如:给定一个3×5的矩阵:

121231045353410

它的和最大子矩阵是:
[4553]

最后输出和的最大值为17.
当然这里要求的是一个方阵,还有一种更一般的是矩阵中有正有负,对于子矩阵的大小也没有要求:
0941221876400212

那么最大子矩阵是:
941218


那么如何求解这个问题呢?
如果采用暴力法——遍历所有子矩阵,那么需要找出所以子矩阵,确定子矩阵需要四个参数,左上角两个,右下角两个所以有O(n4)的复杂度,再加上对每个矩阵求和为O(n2)的复杂度,所以暴力法的复杂度约为O(n6).
我们在前面已经说到过,最大连续子数组和最大连续子矩阵的和这个问题是有相关性的。当我们确定矩阵的哪几行时,我们如何确定哪几列呢?,例如当我们要选取2,3,4行时,我们需要决定选取那几列的时候,我们只要将这个三行相加,得到一个一维数组,那么就可以用上一个问题的方法去解决了。
所以思路就是:我们遍历所有的行的组成情况,然后将选出来的行按列相加,构成一个一位数组的最大连续子数组问题。


int maxSubMatrix(int a[],int m,int n)//二维数组以一位数组的形式存放,m和n分别代表矩阵的行和列{    int max=0;    int sum=-10000;  //选择一个足够小的数    int* p=new int[n];   //开辟一个用于存放和的一位数组    for(int i=0;i<m;i++)    {        memset(p,0,sizeof(int)*n);  //赋初值为0        for(int j=i;j<m;j++)        {            for(int k=0;k<n;k++)            {                p[k]+=a[j*n+k];  //累加求和            }            max=maxSubArray(p,n);  //调用一维数组的方法            if(max>sum)                sum=max;        }    }    delete [] p;    return sum;}

测试:

int main(){        int s[]={0,-2,-7,1,9,2,-6,2,-4,1,-4,1,-1,8,0,-2};        cout<<maxSubMatrix(s,4,4)<<endl;        return 0;}

结果是:15


延伸问题:长度最短连续子序列问题

有一个长度为n的正整数序列,现给定一个整数S,要求求出序列中长度最短的一个连续序列,且序列的和大于等于S。

分析:可以直接用两个for循环枚举所有子序列的起点和终点,但这种方法的时间复杂度为$O(n^3)$,需要找到更好的办法。其实,因为都是正数,所以当从第一个数开始累加,累加到大于S的时候,开始删掉前面的数,直到小于S,然后把后面的数加进来,这样遍历一遍就能解决掉。
int LessSeq(const int a[],int N,int s){    int start,end,sum;    start=end=sum=0;    int L=N+1;    while(end<N)    {        if(sum<s)            sum+=a[end];        while(sum>=s)        {            sum-=a[start];            L=min(L,end-start+1);            start++;        }        end++;    }    return L;}
原创粉丝点击