单调队列&单调栈

这篇具有很好参考价值的文章主要介绍了单调队列&单调栈。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单调队列&单调栈

在一些问题中,可以使用单调队列或者单调栈优化
时间复杂度一般会被优化为\(O(n)\)

单调队列

讲解

单调队列: 队尾可以进队出队,对头可以出队(维护队列的单调性,往往会配合二分进一步降低时间复杂度)

  1. 队尾出队的条件是:队列不空且新元素更优,队中的旧元素队尾出队
  2. 每个元素必然从队尾进队一次
  3. 队头出队的条件:队头元素滑出了串口

队列中存储元素的下标,方便判断队头出队

练习

题目1

LeetCode 239. 滑动窗口最大值

class Solution {
public:
    const static int N = 1e5 + 10;
    int q[N], h, t = -1;
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans;
        for(int i = 0; i < nums.size(); i ++ ){
            // 1. 入,当当前元素大于等于队尾元素时,队列不为空,弹出队尾元素,然后加入当前元素
            while(h <= t && nums[i] >= nums[q[t]]) t -- ; // ①
            q[ ++ t] = i;
            // 2. 移除出窗口的元素 0 1 2 3
            if(h <= t && q[h] < i - k + 1) h ++ ; // ② 两条语句交换位置无影响
            // 3. 记录答案
            if(i >= k - 1) ans.push_back(nums[q[h]]);
        }
        return ans;
    }
};

题目2

Luogu P1886 滑动窗口 /【模板】单调队列
模板1

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N], q[N]; // q数组寸元素下标,方便判断队头元素滑出窗口
int main(){
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    int h = 1, t = 0; // 对头、队尾
    for(int i = 1; i <= n; i ++ ){
        while(h <= t && a[q[t]] >= a[i]) t -- ; // 队列不为空,队尾元素>=新进窗口的元素,队尾弹出队列
        q[ ++ t] = i; // 入队
        if(q[h] < i - k + 1) h ++ ; // 如果划出窗口队头出队
        if(i >= k) printf("%d ", a[q[h]]);
    }
    puts("");
    h = 1, t = 0; // 对头、队尾
    for(int i = 1; i <= n; i ++ ){
        while(h <= t && a[q[t]] <= a[i]) t -- ; // 队列不为空,队尾元素>=新进窗口的元素,队尾弹出队列
        q[ ++ t] = i; // 入队
        if(q[h] < i - k + 1) h ++ ; // 如果划出窗口队头出队
        if(i >= k) printf("%d ", a[q[h]]);
    }
    return 0;
}

模板2

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N], q[N], h, t = -1; // h为队列头部,t为队列尾部,如果t >= h则队列不为空
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    for(int i = 0; i < n; i ++ ){
        // 如果队列不为空,且头部元素出队列
        if(h <= t && q[h] < i - m + 1) h ++ ;
        // 如果当前值>=队尾元素,出队列
        while(h <= t && a[i] <= a[q[t]]) t -- ;
        q[ ++ t] = i;
        if(i >= m - 1) printf("%d ", a[q[h]]);
    }
    puts("");
    h = 0, t = -1;
    for(int i = 0; i < n; i ++ ){
        // 如果队列不为空,且头部元素出队列
        if(h <= t && q[h] < i - m + 1) h ++ ;
        // 如果当前值>=队尾元素,出队列
        while(h <= t && a[i] >= a[q[t]]) t -- ;
        q[ ++ t] = i;
        if(i >= m - 1) printf("%d ", a[q[h]]);
    }
    return 0;
}

题目2

LeetCode 53. 最大子数组和
解法1:
单调队列


解法2:
动态规划
解法3:
线段树

class Solution {
public:

    struct status {
        int sum, s, ls, rs; // 区间总和, 最大子段和, 最大前缀和, 最大后缀和
    };

    status build(vector<int>& nums, int l, int r) {
        if (l == r) return {nums[l], nums[l], nums[l], nums[l]};

        int mid = l + r >> 1;
        auto L = build(nums, l, mid), R = build(nums, mid + 1, r);
        status LR;
        LR.sum = L.sum + R.sum;
        LR.s = max(max(L.s, R.s), L.rs + R.ls);
        LR.ls = max(L.ls, L.sum + R.ls);
        LR.rs = max(R.rs, R.sum + L.rs);
        return LR;
    }

    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        auto res = build(nums, 0, n - 1);
        return res.s;
    }
};

题目三

Acwing 6. 多重背包问题 III
解法一

#include<bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int f[N][N], q[N];
int main(){
    int n, m, v, w, s;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++ ){
        scanf("%d%d%d", &v, &w, &s);
        for(int j = 0; j < v; j ++ ){
            int h = 0, t = -1;
            for(int k = j; k <= m; k += v){
                if(h <= t && q[h] < k - s * v) h ++ ;
                if(h <= t) f[i][k] = max(f[i - 1][k], f[i - 1][q[h]] + (k - q[h]) / v * w);
                while(h <= t && f[i - 1][k] >= f[i - 1][q[t]] + (k - q[t]) / v * w) t -- ;
                q[ ++ t] = k;
            }
        }
    }
    printf("%d", f[n][m]);
    return 0;
}

为什么不对 ? 因为f[i][j]的更新顺序并不是线性的所以答案是不正确的,而使用复制数组g便满足了线性更新,保证了答案的正确

解法二

#include<bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int f[N], g[N], q[N];
int main(){
    int n, m, v, w, s;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++ ){
        memcpy(g, f, sizeof f);
        scanf("%d%d%d", &v, &w, &s);
        for(int j = 0; j < v; j ++ ){
            int h = 0, t = -1;
            for(int k = j; k <= m; k += v){
                if(h <= t && q[h] < k - s * v) h ++ ;
                if(h <= t) f[k] = max(g[k], g[q[h]] + (k - q[h]) / v * w);
                while(h <= t && g[k] >= g[q[t]] + (k - q[t]) / v * w) t -- ;
                q[ ++ t] = k;
            }
        }
    }
    printf("%d", f[m]);
    return 0;
}

单调栈

练习

题目1

LeetCode 739. 每日温度
逆序更新

class Solution {
public:
    const static int N = 1e5 + 10;
    int stk[N], t;
    void push(int x){
        stk[t ++ ] = x;
    }
    void pop(){
        t -- ;
    }
    bool empty(){
        return t <= 0;
    }
    int peek(){
        return stk[t - 1];
    }
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> ans(n);
        for(int i = n - 1; i >= 0; i -- ){
            int t = temperatures[i];
            while(!empty() && t >= temperatures[peek()]) pop();
            if(!empty()) ans[i] = peek() - i;
            push(i);
        }
        return ans;
    }
};

正序更新文章来源地址https://www.toymoban.com/news/detail-747693.html

class Solution {
public:
    const static int N = 1e5 + 10;
    int stk[N], t;
    void push(int x){
        stk[t ++ ] = x;
    }
    void pop(){
        t -- ;
    }
    bool empty(){
        return t <= 0;
    }
    int peek(){
        return stk[t - 1];
    }
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> ans(n);
        for(int i = 0; i < n; i ++ ){
            while(!empty() && temperatures[i] > temperatures[peek()]){
                int j = peek();
                pop();
                ans[j] = i - j;
            }
            push(i);
        }
        return ans;
    }
};

到了这里,关于单调队列&单调栈的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【学习笔记】单调队列和单调栈

    以这道题为例:P5788。我们考虑维护一个单调栈,里面存的是下标,使里面的下标对应的元素从栈顶到栈底是单调上升的。 我们从 (nrightarrow 1) 枚举 (a_i) 对于每个 (i) ,如果栈非空,令栈顶的下标为 (j) ,若 (a_j) 不比 (a_i) 大,那么这个栈顶元素由于值又小,位置又靠

    2024年02月15日
    浏览(38)
  • 算法模板之单调栈和单调队列图文详解

    🌈个人主页: 聆风吟 🔥系列专栏: 算法模板、数据结构 🔖少年有梦不应止于心动,更要付诸行动。      💬 hello! 各位铁子们大家好哇,今天作者给大家带来了单调栈和单调队列的算法模板讲解,让我们一起加油进步。      📚 系列专栏:本期文章收录在《算法

    2024年02月02日
    浏览(46)
  • 蓝桥杯每日一题----单调栈和单调队列

    单调栈即栈内的元素是单调递减或者单调递增的,我们通过一个题目来理解。 单调栈模板题 题目描述 给出项数为 n 的整数数列 a 1 … a n a_1…a_n a 1 ​ … a n ​ 。 定义函数 f ( i ) f(i) f ( i ) 代表数列中第 i 个元素之后第一个大于 a i a_i a i ​ 的元素的下标,即 f ( i ) = m i n i

    2024年02月19日
    浏览(42)
  • 辅助栈、单调栈与单调队列在lc中的应用

    三者都有何区别? 个人建议 单调栈/队列 中存放的元素最好是下标而不是值,因为有的题目需要根据下标计算,这样泛化性更好。参考lc239和lc496 大概的思路是先把不满足单调性的元素poll掉,然后offer一个当前符合条件的元素。参考lc239和lc496 // 说明:这里使用两个堆来维护

    2024年02月14日
    浏览(37)
  • leetcode分类刷题:队列(Queue)(一、单调队列)

    单调队列,看起来是与单调栈对应起来的一样;但是做题的时候感觉单调队列不像单调栈一样,能根据题意自然形成 单调队列的基本实现 ,感觉单调队列更像是和某个队列对应起来的一样 1、 单调队列的经典题型 :使用双向队列维护窗口,窗口移动的元素增删与队列的先进

    2024年02月09日
    浏览(41)
  • 多重背包问题——单调队列优化

    我们在之前的文章中曾经讲解过多重背包问题,当时我们讲解了两种方法,一种方法就是三重循环,这种方法最为朴素好想。但是这种方法的时间复杂度非常高,后来我们想到了二进制优化的方式。那么今天我们将再介绍一种更好的优化方式——单调队列优化。 在讲解这种优

    2024年02月15日
    浏览(42)
  • 算法设计-单调队列

    这个东西我弄得还不是很明白,所以先放一篇博客:https://www.cnblogs.com/I-Love-You-520/p/13454305.html ​ 单调队列说的就是博客中提到的场景,可以说解决的是 固定长度,多次查询一段的最值 (其实就是slice window的意思),这种如果是朴素算法的话,因为查一段序列的最值的时间复

    2023年04月08日
    浏览(33)
  • 单调队列

    2024年02月19日
    浏览(29)
  • C - 滑动窗口 /【模板】单调队列

    Description 有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。 例如: The array is [1,3,−1,−3,5,3,6,7] and k=3。 Input 输入一共有两行,第一行有两个正整数 n,k。 第二行 n 个整数

    2024年02月10日
    浏览(36)
  • 单调队列-滑动窗口最大值

    Problem: 239. 滑动窗口最大值 输入一个数组nums,滑动窗口k遍历该数组,输出得到的最大值数组; 示例1: 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 构造一个单调队列表示当前窗口中单调递减的队列,队列的头就是最大值,为保证这个队列是窗口数据的表示,每次判断队

    2024年02月22日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包