ACM-最大子序列和

最大子序列和问题,即给出一个序列a1,a2,a3,...,an,问该序列的所有子序列中,各项相加后和最大是多少,甚至还可能要求给出该子序列的起始位置和终止位置。

算法一:

最容易想到、直观的算法,就是枚举子序列的任何一个起点和终点位置,这样一定可以得到正确答案,并且也能求出子序列的位置,但同时也可以确定的是这样做的时间复杂度太高,达到了o(n^3),程序执行效率不高,一般都会超过时间限制。实现代码如下:

// o(n^3)算法
int MaxSubsequenceSum(int *seque, int size)
{
    int maxSum = seque[0];
    // i为子序列的左边界
    for(int i=0; i<size; ++i)
    {
        // j为子序列的右边界
        for(int j=i; j<size; ++j)
        {
            int subSum = 0;
            // 迭代子序列中的每一个元素,求和
            for(int k=i; k<=j; ++k) subSum += seque[k];
            if(subSum > maxSum) maxSum = subSum;
        }
    }
    return maxSum;
}

算法二:

算法一使用了三层循环,但其实最里面的那一层循环,即通过循环来计算某一个子序列的和,这里是可以避免的,可以使用累加的思想来代替循环求取子序列的和,即将复杂度降为o(n^2)。试想如果起点i确定,在枚举终点j的时候,由于j是逐渐增大的,所以当前的sum(i,j),不就是前面计算过的sum(i,j-1),然后再加上aj么。实现代码如下:

// o(n^2)算法
int MaxSubsequenceSum(int *seque, int size)
{
    int maxSum = seque[0];
    for(int i=0; i<size; ++i)
    {
        // 维护前缀和
        int subSum = 0;
        for(int j=i; j<size; ++j)
        {
            subSum += seque[j];
            if(subSum > maxSum) maxSum = subSum;
        }
    }
    return maxSum;
}

算法三:

这里介绍一种o(nlongn)的算法,其根本思想是分治。根据分治三部曲,首先将序列不断二分,最终和最大的子序列出现的位置不过以下三种可能:

1、完全在左半部

2、完全在右半部

3、跨越左右两个部分

对于1、2情况,直接递归就可以找出正确答案;对于3情况,可以以中点为中心,向左算出包含左半部分最后一个元素的最大子序列和,向右算出包含右半部份第一个元素的最大子序列和,那么答案就是它们的和。实现代码如下:

// o(nlogn)算法
int Max3(int a, int b, int c)
{
    // 返回三个数中的最大值
    if(a < b) a = b;
    if(a > c) return a;
    else return c;
}
int MaxSubsequenceSum(int *seque, int left, int right)
{
    if(left == right)
    {
        if(seque[left] > 0) return seque[left];
        // 保证最小值为0
        else return 0;
    }

    int mid = left + (right-left)/2;
    // 递归调用,求左部分的最大和
    int maxLeftSum = MaxSubsequenceSum(seque, left, mid);
    // 递归调用,求右部分的最大和
    int maxRightSum = MaxSubsequenceSum(seque, mid+1, right);

    // 定义左边界子序列的和
    int leftBorderSum=0, maxLeftBorderSum=0;
    for(int i=mid; i>=left; --i)
    {
        leftBorderSum += seque[i];
        if(leftBorderSum > maxLeftBorderSum)
        {
            maxLeftBorderSum = leftBorderSum;
        }
    }

    // 定义右边界子序列的和
    int rightBorderSum=0, maxRightBorderSum=0;
    for(int i=mid+1; i<=right; ++i)
    {
        rightBorderSum += seque[i];
        if(rightBorderSum > maxRightBorderSum)
        {
            maxRightBorderSum = rightBorderSum;
        }
    }

    // 选出这三者中的最大值并返回
    return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum+maxRightBorderSum);
}

算法四:

其实对于最大子序列和问题,还有时间复杂度仅为o(n)的算法,此算法基于以下分析:

如果ai是负数,那么它不可能代表最优序列的起点,因为任何包含ai的作为起点的子序列都可以通过使用a[i+1]作为起点得到改进。类似的,任何负的子序列也不可能是最优子序列的前缀(原理相同)。如果在内循环中检测到从ai到aj的子序列的和是负数,那么可以向后推进i。关键的结论是:我们不仅能够把i推进到 i+1,而且实际上我们还可以把它一直推进到j+1。该算法的一个附带优点是:它只对数据进行一次扫描,一旦ai被读入并处理,它就不再需要被记忆,并且在任意时刻,算法都能对它已经读入的数据给出最大子序列和问题的正确答案,具有这种特性的算法叫做“联机算法”,仅需要常量空间并以线性时间运行。实现代码如下:

// o(n)算法,但无法求出位置
int MaxSubsequenceSum(int *seque, int size)
{
    int maxSum=seque[0], subSum=0;
    for(int i=0; i<size; ++i)
    {
        subSum += seque[i];
        if(subSum > maxSum) maxSum = subSum;
        else if(subSum < 0) subSum = 0;
    }
    return maxSum;
}

算法五:

算法四的时间复杂度是最优的了,但是它有一个不足,就是只能求出最大子序列的和,而不能求出其位置,因为在实现上并没有记录终点。这里再讨论另外一种算法,时间复杂度同样是o(n^2),但是同时也能求出位置信息。其算法实现依赖的是动态规划的思想,设subSum[i]为以a[i]终止且包含a[i]的最大序列的和,则有:

1、如果subSum[i]<=0,那么对于subSum[i+1]来说,它将没影响,甚至减少subSum[i+1]的值,那么subSum[i+1]不应该考虑加上它,而应该直接等于a[i+1]

2、否则,如果subSum[i]>0,那么它将增加subSum[i+1]的值,所以subSum[i+1]应该等于subSum[i] + a[i+1]

所以,可以得出转移方程:subSum[1] = a[1],subSum[i+1] = subSum[i]>0 ? subSum[i]+a[i+1] : a[i+1]。实现代码如下:

// o(n)算法
int MaxSubsequenceSum(int *seque, int size, int &left, int &right)
{
    int start, maxSum, subSum;
    // 初始化当前子序列和最大子序列为seque[0]
    start = left = right = 0;
    maxSum = subSum = seque[0];

    for(int i=1; i<size; ++i)
    {
        if(subSum > 0) subSum += seque[i];
        else
        {
            // 抛弃当前子序列
            subSum = seque[i];
            // 开始新的子序列搜索
            start = i;
        }

        // 更新最大子序列
        if(maxSum < subSum)
        {
            maxSum = subSum;
            left = start;
            right = i;
        }
    }
    return maxSum;
}

对于最大子序列和问题还有一种扩展,即将一维序列扩展为二维序列,详见下一篇文章,传送门(点击打开链接)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值