最大子序列和问题四种解(O)

方法一:

               最简单粗暴的想法,以每个点i为起点,以i后面的每个点为终点,求和取最小

       for (i = 0 -> n) for (j = i -> n) for (k = i->j) 三重循环,时间复杂度O(N^3)

#include<bits/stdc++.h>
using namespace std;
int MaxSubsequenceSum(const int A[],int N)
{
    int ThisSum,MaxSum = 0;
    for (int i = 0;i < N;++i)
    {
        for (int j = i;j < N;++j)
        {
            ThisSum = 0;
            for (int k = i;k <= j;++k)
                ThisSum += A[k];
            if (ThisSum > MaxSum)
                MaxSum = ThisSum;
        }
    }
    return MaxSum;
}
int main()
{
    int A [8] = {4,-3,5,-2,-1,2,6,-2};
    cout<<MaxSubsequenceSum(A,8)<<endl;
    return 0;
}

方法二:

算是对于方法一的一个优化,在方法一的三个循环中,第三个循环每次都会重复之前的操作,于是将第三个循环省略

(减少重复工作,将需要多次用的计算部分记录下来是一种很重要的优化思想)时间复杂度O(N^2)

#include<bits/stdc++.h>
using namespace std;
int MaxSubsequenceSum(const int A[],int N)
{
    int ThisSum,MaxSum = 0;
    for (int i = 0;i < N;++i)
    {
        ThisSum = 0;
        for (int j = i;j < N;++j)
        {
            ThisSum += A[j];//这里将之前每次计算的结果利用下来
            if (ThisSum > MaxSum)
                MaxSum = ThisSum;
        }
    }
    return MaxSum;
}
int main()
{
    int A [8] = {4,-3,5,-2,-1,2,6,-2};
    cout<<MaxSubsequenceSum(A,8)<<endl;
    return 0;
}

方法三:

这个问题里用到的分冶方法算是我第一次接触到分冶,之后学习归并排序也是利用分冶,再后来学线段树,感觉线段树也是一种分冶思想

所谓分冶,就是将一个问题分成两个子问题,递归的求解子问题,再将两个子问题的解合并到一起得到整个问题的解

在这个问题中,最大子序列的和可能出现在三处:或者出现在输入数据的左半部,或者出现在后半部,或者跨越中部从而占据左右两部分

对于第三种情况,可以求解左部包含最后一个元素的最大和,右部包含第一个元素的最大和,然后将这两个和加在一起(是否联想到线段树的区间合并了呢?)

例如: 4    -3    5    -2            -1    2    6    -2

前半部的最大和是6(4,-3,5),后半部的最大和是8(2,6)前半部包含最后一个元素的最大和是4,后半部包含第一个元素的最大和是7

因而最后的结果是4+7 = 11  时间复杂度为O(NlogN)

PS:个人很看重方法三,记得做过很多线段树区间合并的题都用的类似的原理

线段树的区间合并常常是记录包含左端点的值,包含右端点的值,以及区间内的值

随便来个线段树区间合并的例子:点击打开链接


#include<bits/stdc++.h>
using namespace std;
static int MaxSubSum(const int A[],int Left,int Right)
{
    int MaxLeftSum,MaxRightSum;
    int MaxLeftBorderSum,MaxRightBorderSum;
    int LeftBorderSum,RightBorderSum;
    int Center,i;
    if (Left == Right)
    {
        if (A[Left] > 0) return A[Left];
        else return 0;
    }
    Center = (Left+Right)>>1;
    MaxLeftSum = MaxSubSum(A,Left,Center);//递归求解左
    MaxRightSum = MaxSubSum(A,Center+1,Right);//递归求解右
    MaxLeftBorderSum = 0;LeftBorderSum = 0;
    for (i = Center;i >= Left;--i)
    {
        LeftBorderSum += A[i];
        if (LeftBorderSum > MaxLeftBorderSum)
        {
            MaxLeftBorderSum = LeftBorderSum;
        }
    }
    MaxRightBorderSum = 0;RightBorderSum = 0;
    for (i = Center+1;i <= Right;++i)
    {
        RightBorderSum += A[i];
        if (RightBorderSum > MaxRightBorderSum)
        {
            MaxRightBorderSum = RightBorderSum;
        }
    }
    return max(max(MaxLeftSum,MaxRightSum),MaxLeftBorderSum+MaxRightBorderSum);
}
int MaxSubsequenceSum(const int A[],int N)
{
    return MaxSubSum(A,0,N-1);
}
int main()
{
    int A [8] = {4,-3,5,-2,-1,2,6,-2};
    cout<<MaxSubsequenceSum(A,8)<<endl;
    return 0;
}


方法四:

如果没有方法四,我们可以很清晰的看到递归的好处,但是美丽的DP思想让方法四完美到无懈可击

在求最大子序列和的时候,我们注意到,只要是正数就可以为我们的结果增益,而负数则舍弃

时间复杂度O(N),同时代码不长

#include<bits/stdc++.h>
using namespace std;
int MaxSubsequenceSum(const int A[],int N)
{
    int ThisSum = 0, MaxSum = 0;
    for (int i = 0;i < N;++i)
    {
        ThisSum += A[i];
        if (ThisSum > MaxSum)
            MaxSum = ThisSum;
        else if (ThisSum < 0)
            ThisSum = 0;
    }
    return MaxSum;
}
int main()
{
    int A [8] = {4,-3,5,-2,-1,2,6,-2};
    cout<<MaxSubsequenceSum(A,8)<<endl;
    return 0;
}


运用:点击打开链接


参考资料:

《数据结构与算法分析 -- C语言描述》

PS:

当初看一位大神的成长之路就推荐了这本书,大神说看到书中将一个O(N^3)的算法一路杀到O(N),当时心中那个崇拜之情。。。想来大神说的就是这个例子

后来我也买了这本书,可惜看之前已经学了不少算法,所以对此没有太强烈的感觉,但是还是能深刻体会到算法的强大魅力

大神的成神之路:我的算法学习之路

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值