最大公共子序列求和问题(MCSS)

最近开始复习数据结构,准备把《数据结构与算法分析:C语言描述》这本书重刷一遍,顺便做点笔记。

GitHub地址:https://github.com/Chaomin702/Algorithm.git

问题描述

最大公共子序列求和(Maximum Contiguous Subsequence Sum):
给定整数 A1,A2,...,AN (可能有负数),求 jk=iAk 的最大值(为方便起见,如果所有整数均为负数,则最大子序列和为0)。
比如样例集合为 {2,11,4,13,5,2} ,最长子序列和应为20。

暴力求解

最直接可以想到的一种解法就是:枚举所有的公共序列,计算最大和即可。即:

maxsj,k=i=jkai(0j,k<N)

思路很简单,直接给出代码。

int MaxSubsequenceSumForce(const int A[], int N){
    int maxSum = 0, thisSum = 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;
}

动态规划

Fi 为以元素 i 结尾的连续最大子序列的和。
那么对于第 i 个元素来说,要形成连续的最大子序列和,只和相邻的前一个元素有关。如果 Ai 加上以 Ai1 结尾的最大连续子序列和 Fi1 后为负数,那么 Fi=0 ,否则 Fi=Fi1+Ai
也就是:

Fi=max(0,Fi1+Ai)

其中

F0=0

下面给个求解过程来感受一下。

i123456
Ai -211-413-5-2
Fi 0117201513

上述解法的正确性很容易用数学归纳法证明,在此略过。下面附上代码:

int MaxSubsequenceSumDP1(const int A[], int N){
    int f = 0, maxSum = 0;
    for (int i = 0; i < N; i++){
        f += A[i];
        if (f < 0)
            f = 0;
        if (maxSum < f)
            maxSum = f;
    }
    return maxSum;
}

分而治之

轮到Divide and Conquer这个强大的工具出场了。

其实,最大连续序列和可能会在三处出现:

  1. 输入数据的前半部分
  2. 输入数据的后半部分
  3. 跨越输入数据的中部而占据左右各半部分。

其中前两种情况可以递归求解,第3种情况可以通过求出前半部分的最大和(包含前半部分的最后一个元素)和后半部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和加在一起。
最后,选出这三个最大和的最大者即可。
好,下面附上代码。

int MaxSubSumDC(const int A[], int l, int h){
    if (h - l < 1){
        if (A[l]< 0)
            return 0;
        else
            return A[l];
    }
    int centre = (l + h) / 2;
    int leftMax = MaxSubSumDC(A, l, centre);
    int rightMax = MaxSubSumDC(A, centre + 1, h);
    int centreLeftMax = 0, centreLeftSum = 0;
    for (int i = centre; i>=l; i--){
        centreLeftSum += A[i];
        if (centreLeftSum > centreLeftMax){
            centreLeftMax = centreLeftSum;
        }
    }
    int centreRightMax = 0, centreRightSum = 0;
    for (int i = centre + 1; i <= h; i++){
        centreRightSum += A[i];
        if (centreRightSum > centreRightMax)
            centreRightMax = centreRightSum;
    }
    int centreMax = centreLeftMax + centreRightMax;
    return max(max(leftMax, rightMax), centreMax);
}

时间复杂度分析

暴力法动态规划的时间复杂付毋庸置疑,分别是 O(n2)O(n)
分治法略微有些复杂:

{T(1)=1T(n)=T(n/2)+n

也就是

O(nlogn)

点赞