【数据结构】单调队列专题(滑动窗口问题)

学习资料

1.单调队列 滑动窗口最大值【基础算法精讲 27】
2.E11【模板】单调队列 滑动窗口最值
3.算法讲解054【必备】单调队列-上


首先明确一点,队列中存的是下标,不是数组中的值,数组中的值是num[q[tt]]num[q[hh]]
三步走
1.判断队头出窗口
在队列不空时,判断队头是否出窗口:i - q[hh] + 1 > k或者STL中i - q.front() + 1 > k,若队头已经出了窗口,弹出队头hh++q.pop_front(),i为当前遍历到的下标,q[hh]q.front()表示队头下标,k为窗口大小

判断条件:if (i - q[hh] + 1 > k) hh++;
STL版本:if (i - q.front() + 1 > k) q.pop_front();

2.判断队尾是否有可能成为答案,并把当前遍历元素下标加入队尾
在队列不空时,不断比较队尾与当前遍历元素的大小关系,判断队尾是否永远用不到了(看求窗口最大值还是最小值),队尾永远用不到就弹出队尾,最小值判断条件num[q[tt]] >= num[i]就说明队尾永无出头之日,弹出,最大值判断条件num[q[tt]] <= num[i],弹出
判断条件:while (hh <= tt && num[q[tt]] <= or >= num[i]) tt--;
STL版本:while (q.size() && num[q.back()] <= or >= num[i]) q.pop_back();
当前元素下标加入队尾:q[++tt] = i,STL版本q.push_back(i)
3.判断什么时候可以输出答案
以样例为例子,窗口大小k=3,数组下标从0开始,第一组答案的最后一个元素-1的下标i2,所以可利用下标与窗口大小k的关系i >= k - 1来判断是否可以输出答案。在样例中就是2>=3-1

判断条件:if (i >= k - 1) printf("%d ",num[q[hh]]) or res.push_back(num[q[hh]]);
STL版本:if (i >= k - 1) printf("%d ",num[q.front()]) or res.push_back(num[q.front()]);

Snipaste_2025-01-14_14-24-34.png

练习题

一维滑动窗口

Leetcode239. 滑动窗口最大值

const int N = 1e5 + 10;
int q[N];
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& a, int k) {
        int n = a.size();
        vector<int> res;
        int hh = 0, tt = -1;
        for (int i = 0; i < n; i++)
        {
            if (hh <= tt && i - q[hh] + 1 > k) hh++;
            while (hh <= tt && a[q[tt]] <= a[i]) tt--;
            q[++tt] = i;
            if (i >= k - 1) res.push_back(a[q[hh]]); 
        }
        return res;
    }
};

优先队列写法

class Solution {
    using PII = pair<int, int>;
public:
    vector<int> res;
    int n;
    priority_queue<PII> heap;
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        n = nums.size();
        for (int i = 0; i < k; i++) heap.push({nums[i], i});
        res.push_back(heap.top().first);
        for (int i = k; i < n; i++)
        {
            heap.push({nums[i], i});
            while (i - heap.top().second + 1 > k) heap.pop();
            res.push_back(heap.top().first);
        }
        return res;
    }
};

154. 滑动窗口

下标从0开始,数组模拟队列

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, k;
int a[N], q[N];

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    int hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    puts("");
    hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    return 0;
}

作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/1161854/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

下标从0开始,STL队列

#include <iostream>
#include <deque>

using namespace std;

const int N = 1e6 + 10;

int n, k;
int a[N];

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    deque<int> q;
    for (int i = 0; i < n; i ++ )
    {
        if (q.size() && i - (k - 1) > q.front()) q.pop_front();
        while (q.size() && a[q.back()] >= a[i]) q.pop_back();
        q.push_back(i);
        if (i >= k - 1) printf("%d ", a[q.front()]);  // 注:输出的是队头元素
    }
    printf("\n");
    q = deque<int>();
    for (int i = 0; i < n; i ++ )
    {
        if (q.size() && i - (k - 1) > q.front()) q.pop_front();
        while (q.size() && a[q.back()] <= a[i]) q.pop_back();
        q.push_back(i);
        if (i >= k - 1) printf("%d ", a[q.front()]); // 注:输出的是队头元素
    }
    return 0;
}

作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/1161854/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

下标从1开始,数组模拟队列

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, k;
int q[N], a[N];

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);

    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ )
    {
        if (hh <= tt && i - k >= q[hh]) hh ++ ; // 也可写成i - k + 1 > q[hh]
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k) printf("%d ", a[q[hh]]);
    }
    puts("");
    hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ )
    {
        if (hh <= tt && i - k >= q[hh]) hh ++ ; // 也可写成i - k + 1 > q[hh]
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k) printf("%d ", a[q[hh]]);
    }
    return 0;
}

作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/1161854/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二维滑动窗口

1091. 理想的正方形

#include <iostream>

using namespace std;

const int N = 1010;

int n, m, k;
int w[N][N], row_max[N][N], row_min[N][N];
int q[N];

void get_max(int a[], int b[], int tot)
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k - 1) b[i] = a[q[hh]];
    }
}

void get_min(int a[], int b[], int tot)
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k - 1) b[i] = a[q[hh]];
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            scanf("%d", &w[i][j]);

    for (int i = 0; i < n; i ++ ) // 枚举每一行的最值
    {
        get_max(w[i], row_max[i], m);
        get_min(w[i], row_min[i], m);
    }

    int a[N], b[N], c[N], res = 1e9;
    for (int i = k - 1; i < m; i ++ )
    {
        for (int j = 0; j < n; j ++ ) a[j] = row_max[j][i];
        get_max(a, b, n);

        for (int j = 0; j < n; j ++ ) a[j] = row_min[j][i];
        get_min(a, c, n);

        for (int j = k - 1; j < n; j ++ )
            res = min(res, b[j] - c[j]);
    }
    printf("%d\n", res);
    return 0;
}

作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/1600528/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4964. 子矩阵

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1010, mod = 998244353;

int n, m, A, B;
int w[N][N];
int row_min[N][N], row_max[N][N];
int q[N];

void get_max(int a[], int b[], int tot, int k) // k为窗口长度,tot为区间总长度
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k - 1) b[i] = a[q[hh]];
    }
}

void get_min(int a[], int b[], int tot, int k) // k为窗口长度,tot为区间总长度
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[++ tt] = i;
        if (i >= k - 1) b[i] = a[q[hh]];
    }
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &A, &B);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            scanf("%d", &w[i][j]);

    for (int i = 0; i < n; i ++ )
    {
        get_max(w[i], row_max[i], m, B);
        get_min(w[i], row_min[i], m, B);
    }

    int res = 0;
    int a[N], b[N], c[N];
    for (int i = B - 1; i < m; i ++ ) // 枚举每一列
    {
        for (int j = 0; j < n; j ++ ) a[j] = row_max[j][i];
        get_max(a, b, n, A);

        for (int j = 0; j < n; j ++ ) a[j] = row_min[j][i];
        get_min(a, c, n, A);

        for (int j = A - 1; j < n; j ++ )
            res = (res + (LL)b[j] * c[j]) % mod;
    }
    printf("%d\n", res);
    return 0;
}

作者:NFYD
链接:https://www.acwing.com/activity/content/code/content/6402744/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2023-05-06 18:04  Tshaxz  阅读(57)  评论(0编辑  收藏  举报
Language: HTML