定长滑动窗口与变长滑动窗口

1.定长滑动窗口

#HZOJ 270. 最大子序和

https://oj.haizeix.com/problem/270icon-default.png?t=N7T8https://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.变长滑动窗口

leetcode 1438. 绝对差不超过限制的最长连续子数组icon-default.png?t=N7T8https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/

给你一个整数数组 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());
    }
};

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值