1.定长滑动窗口
#HZOJ 270. 最大子序和
https://oj.haizeix.com/problem/270https://oj.haizeix.com/problem/270
题目描述
输入一个长度为 n 的整数序列,从中找出一段不超过 M 的连续子序列,使得整个序列的和最大。
例如 1,−3,5,1,−2,3:
当 m=4 时,S=5+1−2+3=7;
当 m=2 或 m=3 时,S=5+1=6。
输入
第一行两个数 n,m。
第二行有 n 个数,要求在 n 个数找到最大子序和。
输出
一个数,数出他们的最大子序和。
样例输入
6 4
1 -3 5 1 -2 3
样例输出
7
数据规模与约定
时间限制:1 s
内存限制:256 M
100% 的数据保证 1≤n≤300000
之前有写过一个类似的“最大子段和”问题,但是与这道题不同的是,那道题并没有给我们设立什么“不能超过m个数”之类的条件,因此那道题可以使用的动态规划思路在这道题中并不适用——限制了字段长度,没办法去递推。
这道题实际上是滑动窗口+单调队列的经典例题。我们的思路是这样的:我们可以想到,用前缀和数组,然后维护一个定长为k的滑动窗口,我们用一个单调队列来维护该窗口中的最小值。我们知道,某段连续子序列之和就为前缀和数组的这一段的结尾值减去开头值。对于每一个长度不超过规定值k的窗口而言,我们将其中的最后一个设为以其为结尾的最长子序和,由于窗口规定了长度,得到的最大子序和一定不会超过k值,也就满足了条件。而单调队列存储的就是这段窗口内的最小值,因此我们用窗口的最后一个值减去这个最小值,得到的就是以该值为结尾的最长子序和。设立一个数组,其中存储以所有值为结尾的最长子序和,最后我们取其中最大值输出即可。
对下面代码注释“//关于为什么只用弹出一次”的解释:我之前的想法是,弹出一次的话,有没有可能后面顶上来的那个数也在我们的滑动窗口之外呢?其实仔细一想,发现没有这个可能。在一个单调(以小为顶)队列中,不可能存在a比b先入队而a还要比b大的情况。因为我们是滑动窗口,在原数组中,同时也在滑动窗口外的那个一定先入队。
#include <iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
//270_最大子序和
int main()
{
int n, m;cin >> n >> m;
vector<int> v(n);
cin >> v[0];
for (int i = 1;i < n;++i)
{
int a;
cin >> a;
v[i] = v[i - 1] + a;
}
deque<int> q;
vector<int> mmax(n);
for (int i = 0;i <= m;i++)
{
while (!q.empty() && v[q.back()] > v[i])
q.pop_back();
q.push_back(i);
if (i == 0)
mmax[i] = v[i];
else mmax[i] = v[i] - v[q.front()];
}
for (int i = m + 1;i < n;i++)
{
while (!q.empty() && v[q.back()] > v[i])
q.pop_back();
q.push_back(i);
if (i - q.front() > m)//关于为什么只用弹出一次
q.pop_front();
mmax[i] = v[i] - v[q.front()];
}
cout << *max_element(mmax.begin(), mmax.end());
return 0;
}
2.变长滑动窗口
给你一个整数数组 nums
,和一个表示限制的整数 limit
,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit
。
如果不存在满足条件的子数组,则返回 0
。
示例 1:
输入:nums = [8,2,4,7], limit = 4 输出:2 解释:所有子数组如下: [8] 最大绝对差 |8-8| = 0 <= 4. [8,2] 最大绝对差 |8-2| = 6 > 4. [8,2,4] 最大绝对差 |8-2| = 6 > 4. [8,2,4,7] 最大绝对差 |8-2| = 6 > 4. [2] 最大绝对差 |2-2| = 0 <= 4. [2,4] 最大绝对差 |2-4| = 2 <= 4. [2,4,7] 最大绝对差 |2-7| = 5 > 4. [4] 最大绝对差 |4-4| = 0 <= 4. [4,7] 最大绝对差 |4-7| = 3 <= 4. [7] 最大绝对差 |7-7| = 0 <= 4. 因此,满足题意的最长子数组的长度为 2 。
示例 2:
输入:nums = [10,1,2,4,7,2], limit = 5 输出:4 解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。
示例 3:
输入:nums = [4,2,2,2,4,4,2,2], limit = 0 输出:3
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
0 <= limit <= 10^9
根据这道题的题面,我们很容易联想到变长滑动窗口内维护最大值和最小值,但要怎么同时维护最大值和最小值呢?我一开始还想了一会这个问题,后来发现,直接维护两个单调队列不就好了吗?一个存储最大值,一个存储最小值。然后这道题的具体解决思路是这样的:首先两个指针分别指向滑动窗口左右两边,然后右指针不断向右移动,右指针指向的值也不断入队,直到我们最大值与最小值的差大于limit(这个时候我们可以知道,最新一个入队的那个值一定是一个最大值或者是最小值,我们接下来要做的,就是找到与之相对的在他之前进入滑动窗口的最值,将之排除在窗口之外)。这个时候我们就将此时l-r的长度存入答案数组,之后我们就让左指针不断向右,直到我们的l指针指向了该区间的最大或最小值,然后将其出队,l++即可。最后循环结束的时候别忘了特判,把最后一个合法区间的长度纳入答案数组。最后返回数组中最大值即可。
class Solution {
public:
int longestSubarray(vector<int>& nums, int limit)
{
vector<int> ans;
int l, r;
l = r = 0;
int n = nums.size();
deque<int> b, s;
do
{
while (!b.empty() && nums[b.back()] < nums[r])
b.pop_back();
b.push_back(r);
while (!s.empty() && nums[s.back()] > nums[r])
s.pop_back();
s.push_back(r);
r++;
if (nums[b.front()] - nums[s.front()] > limit)
{
ans.push_back(r - l - 1);
while (l != b.front() && l != s.front())
l++;
if (l == b.front())
b.pop_front();
else s.pop_front();
l++;
}
} while (r < n);
ans.push_back(r - l);
return *max_element(ans.begin(), ans.end());
}
};