最大子段和问题

发布时间 2023-03-28 19:39:53作者: 光風霽月

0x01 最大连续子段和

1. 题目描述

给定我们一个数组,让我们求最大连续“非空”子段和。

2 贪心 + 递推

思路:

如果我们选取了 k 个数,并且他们的和小于 0 ,那么这 k 个数肯定全都不包含在最大连续子段中。因为它们肯定会使得和更小。

因此,我们可以使用递推的形式,贪心的选择每一个数,如果加上当前数之后和小于 0,那么当前数肯定小于 0,并且,这一段连续子序列我们都不选。

不过,由于题目要求我们必须选择非空的子序列,那么我们就要考虑数组全都死负数的特殊情况了,此时,不存在非空连续区间和不为负数的情况,那么我们最后一个数都不选,得到的结果为 0。这显然是不行的(相当于我们一个数也没选),因为,我们要特判一下。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <limits.h>

using namespace std;

int main()
{
    int n, cur = 0;
    int res = INT_MIN, seq_minx = INT_MIN;
    cin >> n;
    while (n -- )
    {
        int x;  cin >> x;
        if(cur + x < 0) cur = x;
        else    cur = cur + x;
        res = max(res, cur);
    }
    // 特判
    res = max(res, seq_minx);
    cout << res << endl;
    return 0;
}

3. dp

思路:

dp 只不过是递推的一种表现形式罢了,思路和递推差不过,但我们依然严格按照 [闫式 dp 法来分析]

状态方程:f[i] 表示以 i 结尾子段和的最大值。

状态转移:根据是否包含第 i-1 个元素来划分子区间。

  1. 包含第 i-1 个元素,f[i] = max(f[i],f[i-1]+w[i])
  2. 不包含第 i-1 个元素,f[i] = w[i]

另外,对 f[] 的初始化也要注意,求最大值要初始化为最小值,因为最大值可能为负数,所以不能用 0 作为最小值。f[0]=0f[0] 的含义是什么都不选,什么都不选当然为 0 了。

代码:

// 注:代码中有很多可以简化的地方
// 但是为了可读性更好,没有简化
#include <iostream>
#include <cstring>
#include <algorithm>
#include <limits.h>

using namespace std;

const int N = 2e5 + 10;

int n, w[N];
int f[N];
int res = INT_MIN;

int main()
{
    memset(f, -0x3f, sizeof f);
    f[0] = 0;   // !
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> w[i];
        // 包含第 i-1 个元素
        f[i] = max(f[i], f[i - 1] + w[i]);
        // 不包含第 i-1 个元素
        f[i] = max(f[i], w[i]);
        res = max(res, f[i]);
    }
    cout << res << endl;
    return 0;
}

0x02. 最大连续子段和2

1. 题目描述

本题和上一题的差别在于,本题的子段要求最大和不能小于 0,如果数组元素全为负数,规定最大子段和为 0,这意味着最大值至少为 0,其次仍然要求连续

2. 贪心 + 递推

思路:

思路和上一题一模一样,差别在于,本题还需要找到最大连续子段的第一个元素和最后一个元素。如果数组元素全为负数,规定输出数组的第一个元素和最后一个元素。

下面我们讨论不是全负数的情况。

如果加上当前元素和仍然大于 0,当前元素就是尾了。

如果等于 0,因为题目要求输出头更小的,因此此时该元素仍然作为尾。

头稍微不好处理,因为题目要求要求最大连续子段和不能小于 0,所以当前元素小于 0 时,我们不能选它。

代码:

事实证明,把特判数组全负的情况单独拿出来,有利于我们 coding。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2e5 + 10;

int n, w[N];
bool all_minus = true;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )  
    {
        cin >> w[i];
        if(w[i] >= 0)   all_minus = false;
    }
    // 特判全为负数的情况
    if(all_minus)  
    {
        cout << 0 << ' ' << w[1] << ' ' << w[n] << endl;
        return 0;
    }
    
    // head=-1 表示我们的头还没有选
    // 因为最大连续子段和可能为0,因此res要设置为<0的数
    int res = -1, head = -1, tail = -1;
    int cur = 0; // 最大连续子段的和
    int l = -1, r = -1;
    for(int i = 1; i <= n; i ++ )
    {
        if(cur + w[i] < 0) 
        {
            // 此时 w[i]<0,我们肯定不选他
            cur = 0;
            // 标记,我们没有找到头
            head = -1;  
            // 跳过,因为 cur=0 可能更新res=-1
            continue;
        }
        else
        {
            cur += w[i];
            if(head == -1)  head = w[i];
            tail = w[i];
        }
        if(cur > res)
        {
            res = cur;
            l = head;
            r = tail;
        }
    }
    cout << res << ' ' << l << ' ' << r << endl;
    return 0;
}

3. dp

Reference Here

0x02. 拓展

1. 题目描述

本题,要求,非空 + 连续 + 长度有限制

2. 前缀和 + 滑动窗口

非空和连续我们已经能解决了,问题是,长度受限怎么办?

通过一个滑动窗口来维护。

具体的我就不写了,看打开代码的注释或者参考吧。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <limits.h>

using namespace std;

const int N = 3e5 + 10;

int n, m, w[N];
int s[N];
int res;

int main()
{
    cin >> n >> m;
    int seq_maxn = INT_MIN;
    for(int i = 1; i <= n; i ++ )   
    {
        cin >> w[i];
        seq_maxn = max(seq_maxn, w[i]);
        s[i] = s[i - 1] + w[i];
    }
    // 特判全为负数的情况
    if(seq_maxn < 0)
    {
        cout << seq_maxn << endl;
        return 0;
    }
    
    // 滑动窗口依然存储下标
    // 滑动窗口的值是递增的
    deque<int> q;
    q.push_back(0);  // 前缀和,不多做解释了!
    for(int i = 1; i <= n; i ++ )
    {
        // w[i-m+1,i]
        // s[i-m]
        // 注意,我们存储的是前缀和下标,因此q.front()<i-m,而不是i-m-1
        while(q.size() && q.front() < i - m)    q.pop_front();
        while(q.size() && s[i] <= s[q.back()])     q.pop_back();
        q.push_back(i);
        res = max(s[i] - s[q.front()], res);
    }
    cout << res << endl;
    
    return 0;
}

3. 参考

Reference1