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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q__y__L/article/details/77990540

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


  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;
}

猜你喜欢

转载自blog.csdn.net/q__y__L/article/details/77990540