求最大连续子序列的和,两种解法:动态规划 & Kadane算法

这是一个经典问题,对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。

这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为n的数组,其含有2^n个子序列和n(n+1)/2个子串。如果使用穷举法,则至少需要O(n^2)的时间才能得到答案。

该问题是1977年Ulf Grenander提出的一个数字图像方面的问题,1984年卡耐基梅隆大学的Jay Kadane才给出了一个线性时间算法,我们就来看看,如何在线性时间内解决最大子串和问题。

一、Kadane算法 (O(N))

要说明Kadane算法的正确性,需要两个结论。

(1)首先,对于array[1...n],如果array[i...j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i...k]的和大于0。因为如果存在k使得array[i...k]的和小于0,那么我们就有array[k+1...j]的和大于array[i...j],这与我们假设的array[i...j]就是array中和最大子串矛盾。

(2)其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。

此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p...q],是array的和最大子串,且array[p...q],跨越了array[i...j],array[j+1...k]。根据我们的分组方式,存在i<=m<j使得array[i...m]的和是array[i...j]中的最大值,存在j+1<=n<k使得array[j+1...n]的和是array[j+1...k]的最大值。由于array[m+1...j]使得array[i...j]的和小于0。此时我们可以比较array[i...m]和array[j+1...n],如果array[i...m]的和大于array[j+1...n]则array[i...m]>array[p...q],否array[j+1...n]>array[p...q],无论谁大,我们都可以找到比array[p...q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p...q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。

根据上述结论,我们就得到了Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。

下面是实现代码:
int Max::MaxSum3(int *A,int n)//不考虑全负数组,全负数组的最大和子数组为数组最小值  
{  
    int maxNum=0;  
    int sum=0;  
    for(int i=0;i<n;i++)  
    {  
        sum+=A[i];  
        if(sum>maxNum)  
            maxNum=sum;  
        if(sum<0)//当sum<0时,最大和连续子数组要么在A[0,...,i-1]中,要么在A[i+1,...,n-1]中。  
            sum=0;//令sum=0,接下来求A[i+1,...,n-1]的最大和连续子数组  
    }  
    return maxNum;  
}

二、动态规划算法 

定义b[i]为以a[i]结尾的最大后缀序列和,即b[i] = ...... + a[i-1] + a[i]。状态转移方程为:
   b[j]=b[j-1]+a[j];当b[j-1]>0时
   b[j]=a[j];当b[j-1]=0时
通过b[j],我们就可以构造出原问题的最优解,即b[1]~b[n]中的最大者为原问题的最大子序列和。
据此可直接给出动态规划算法:
int MaxSum(int n,int* a){  
    int sum=0,b=0;  
    for(int i=1;i<=n;i++){  
        if(b>0)  //b[i-1]>0  
            b+=a[i];  //b[i]=b[i-1]+a[i];  
        else  //b[i-1]=0  
            b=a[i];  //b[i]=a[i]  
        if(b>sum)  
            sum=b;  
    }  
    return sum;  
}  
该算法耗时O(n),跟Kadane算法复杂度一样。



©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值