算法模板之单调栈和单调队列图文详解

在这里插入图片描述
🌈个人主页:聆风吟
🔥系列专栏:算法模板、数据结构
🔖少年有梦不应止于心动,更要付诸行动。


文章目录

  • 📋前言
  • 一. ⛳️单调栈讲解
    • 1.1 🔔单调栈的定义
    • 1.2 🔔如何维护一个单调栈
    • 1.3 🔔单调栈的用途
    • 1.4 🌟模板总结(重点)🌟
    • 1.5 🔔单调栈的实例练习
  • 二. ⛳️单调队列讲解
    • 2.1 🔔单调队列的定义
    • 2.2 🔔单调队列的用途
    • 2.3 🌟模板总结(重点)🌟
    • 2.4 🔔单调栈的实例练习
  • 📝结语

📋前言

    💬 hello! 各位铁子们大家好哇,今天作者给大家带来了单调栈和单调队列的算法模板讲解,让我们一起加油进步。
    📚 系列专栏:本期文章收录在《算法模板》,大家有兴趣可以浏览和关注,后面将会有更多精彩内容!
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝



一. ⛳️单调栈讲解

1.1 🔔单调栈的定义

定义:栈内的元素是单调递增或单调递减的栈。


1.2 🔔如何维护一个单调栈

  • 单调递增栈:在保持栈内元素单调递增的前提下,如果栈顶元素大于要入栈的元素,将栈顶元素弹出,将新元素入栈。
  • 单调递减栈:在保持栈内元素单调递减的前提下,如果栈顶元素小于要入栈的元素,则将栈顶元素弹出,将新元素入栈。

1.3 🔔单调栈的用途

在这里插入图片描述

由上图可以看出,对于栈内元素来说:

  • 在栈内左边的数就是数组中左边第一个比自己小的元素;
  • 但元素被弹出时,遇到的就是数组中右边第一个比自己小的元素。

对于将要入栈的元素来说:在对栈进行更新后(即弹出了所有比自己大的元素),此时栈顶元素就是数组中左侧第一个比自己小的元素;


在这里插入图片描述

由上图可以看出,对于栈内元素来说:

  • 在栈内左边的数就是数组中左边第一个比自己大的元素;
  • 但元素被弹出时,遇到的就是数组中右边第一个比自己大的元素。

对于将要入栈的元素来说:在对栈进行更新后(即弹出了所有比自己小的元素),此时栈顶元素就是数组中左侧第一个比自己大的元素;

    由此,我们可以看出单调栈的用途是:给定一个序列,指定一个序列中的元素,求解该元素左侧或右侧第一个比自己小或大的元素。

1.4 🌟模板总结(重点)🌟

本文总结的模板是找出每个数左边离它最近的比它大或小的数,右边的可以自行推导(单看模板比较抽象,建议看完下面的题目,在返回来看)

//常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;//栈顶指针
for (int i = 1; i <= n; i ++ )
{
    //check函数是判断查找:
    //1. 每个数左边第一个比它小的数
    //2. 还是每个数左边第一个比它大的数
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i;
}

1.5 🔔单调栈的实例练习

⌈ 在线OJ链接 ⌋

题目:
在这里插入图片描述

输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2

解题思路:
    我们以上面的 3 4 2 7 5 来举例分析,开始遍历 3 这个元素,此时栈为空,那就表明 3 这个元素左侧没有比自身小的元素,将结果 -1 记录一下(或者直接输出)。然后将 3 压入栈中。遍历到 4 时发现 4 大于栈顶的元素 3,表明 4 这个元素左侧第一个比自身小的元素是 3,将结果 3 记录一下(或者直接输出)。遍历到 2 时,发现 2 小于栈顶的元素 4,4 是不可能作为结果输出的,所以需要将栈顶的 4 弹出。弹出之后栈顶的元素就是 3 ,同样 2 仍然小于 3,需要再次将 3 弹出。此时我们发现栈里面已经没有元素了,说明 2 的左侧没有比自身小的元素,将结果 -1 记录一下。然后将 2 压入栈中。其他的元素同理便可以得出,下面给出了动图,这里就不一一讲解了!
在这里插入图片描述

c++代码:

#include <iostream>

using namespace std;
const int N = 100010;
int stk[N], tt;

int main()
{
    int n = 0;
    cin >> n;
    
    for(int i = 0; i < n; i++)
    {
        int x = 0;
        cin >> x;
        
        //如果栈顶元素大于当前待入栈元素,则出栈
        while(tt && stk[tt] >= x) tt--;
        
        if(tt)
        {
            //栈顶元素就是左侧第一个比它小的元素。
            cout << stk[tt] << " ";
        }
        else 
        {
            //如果栈空,则没有比该元素小的值。
            cout << "-1" << " ";
        }
        
        //将当前元素加入单调栈中
        stk[++tt] = x;
    }
    
    return 0;
}


二. ⛳️单调队列讲解

2.1 🔔单调队列的定义

定义:队列内的元素是单调递增或单调递减的队列。


2.2 🔔单调队列的用途

单调队列的用途:在一个滑动窗口中求最值问题


2.3 🌟模板总结(重点)🌟

本文总结的模板主要是找出滑动窗口中的最大值/最小值(单看模板比较抽象,建议看完下面的题目解析,在返回来看)

//常见模型:找出滑动窗口中的最大值/最小值
int hh = 0;//队头 
int tt = -1;//队尾
for (int i = 0; i < n; i ++ )
{
    // 判断队头是否滑出窗口
    while (hh <= tt && check_out(q[hh])) hh ++;
    // 判断队尾元素是否出队列
    while (hh <= tt && check(q[tt], i)) tt --;
    // 将新元素压入队尾
    q[ ++ tt] = i;
}

2.4 🔔单调栈的实例练习

⌈ 在线OJ链接 ⌋

题目:
在这里插入图片描述在这里插入图片描述

输入样例:

8 3
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

解题思路:
    在这里我只讲解下求滑动窗口的最小值,最大值的求解可以类比。当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质(此处是单调增),我们需要不断的将新元素与队尾进行比较,如果新元素小于等于队尾元素,那末队尾元素就可以被永久的移除,我们将其弹出队列。不断重复上述过程直到队列为空或者新元素大于队尾元素时,我们将新元素压入队尾中。由于队列中下标对应的元素是严格单调递增的,因此此时的队头下标对应的元素就是滑动窗口的最小值。
在这里插入图片描述

c++代码:

#include <iostream>

using namespace std;
const int N = 1000010;
//a[N]存放数组元素
//队列q[N]中存的是原数组的下标
int a[N], q[N];
int hh = 0;//队头
int tt = -1;//队尾

int main()
{
    int n, k;
    cin >> n >> k;
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    
    //每个位置滑动窗口中的最小值
    for(int i = 0; i < n; i++)
    {
        if(hh <= tt && i - k + 1 > q[hh]) hh++;//若队首出窗口,hh加1
        while(hh <= tt && a[q[tt]] >= a[i]) --tt;//若队尾不单调,tt减1
        q[++tt] = i;//下标加到队尾
        
        if(i >= k-1) cout << 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) cout << a[q[hh]] << " ";
    }
    
    return 0;
}


📝结语

     今天的干货分享到这里就结束啦!如果觉得文章还可以的话,希望能给个三连支持一下,聆风吟的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的最大动力!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/275039.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JS + CSS 实现高亮关键词(不侵入DOM)

之前在做关键词检索高亮功能的时候&#xff0c;研究了下目前前端实现高亮的几种方式&#xff0c;第一就是替换dom元素实现高亮&#xff0c;第二就是利用浏览器新特性Css.highlights结合js选区与光标与CSS高亮伪类实现&#xff0c;实现功能如下&#xff1a; 一、页面布局 一个…

海德堡UV灯电源维修eta Plus Elc PE22-400-210

uv灯电源维修故障包括&#xff1a; 1、电压不稳&#xff1a;检查uv打印机的电压&#xff0c;设置一个稳压箱即可。 2、温度过高&#xff1a;uv打印机温度过高也会影响uv灯&#xff0c;可以更换为水冷式循环降温。 3、水箱里的信号线接触不好&#xff1a;将两边的信号线对调&…

Python爬虫|使用Selenium轻松爬取网页数据

1. 什么是selenium&#xff1f; Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作浏览器一样。支持的浏览器包括IE&#xff0c;Firefox&#xff0c;Safari&#xff0c;Chrome等。 Selenium可以驱动浏览器自动执…

SpringBoot+ShardingSphereJDBC实战(读写分离,分库分表,垂直拆分、水平拆分)附源码

参考&#xff1a;https://www.51cto.com/article/747736.html https://blog.csdn.net/qq_41581588/article/details/126966665 源码地址&#xff1a;gitgitee.com:jackXUYY/springboot-example.git 读写分离测试 我们启用后缀名为dev的配置文件&#xff0c;如下&#xff0c;…

Visual Studio 2013 中创建一个基于 Qt 的动态链接库:并在MFC DLL程序中使用

在本地已经安装好 Qt 的情况下&#xff0c;按照以下步骤在 Visual Studio 2013 中创建一个基于 Qt 的动态链接库&#xff1a; 一、新建 Qt 项目&#xff1a; 在 Visual Studio 中&#xff0c;选择 “文件” -> “新建” -> “项目…”。在 “新建项目” 对话框中&#…

51和32单片机读取FSR薄膜压力传感器压力变化

文章目录 简介线性电压转换模块51单片机读取DO接线方式51代码实验效果 32单片机读取AO接线方式32代码实验效果 总结 简介 FSR薄膜压力传感器是可以将压力变化转换为电阻变化的一种传感器&#xff0c;单片机可以读取然后作为粗略测量压力&#xff08;仅提供压力变化&#xff0c;…

超时控制:Go语言下的网络请求与时间赛跑

开场白&#xff1a;在互联网的世界里&#xff0c;我们经常要与各种API打交道。有时&#xff0c;这些API可能会因为各种原因而变得“慢条斯理”&#xff0c;这时&#xff0c;超时控制就显得尤为重要了。今天&#xff0c;我们就来聊聊如何在Go语言中实现HTTP请求的超时控制&#…

【JavaScript】DOM事件的传播机制

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

企业如何正确的云迁移,云迁移过程中需要注意哪几个点?

如今的企业比以往任何时候都能访问更多的数据。这些数据正在以惊人的速度增长&#xff0c;无论是数量还是变化量。无论是传统的分析还是机器学习和人工智能等前沿技术&#xff0c;将这些信息从所有信息源集中到云存储库对业务至关重要。 为什么进行迁移&#xff1f; 企业将数…

【JavaScript】异步解决方案的发展历程

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

Flink1.17实战教程(第二篇:DataStream API)

系列文章目录 Flink1.17实战教程&#xff08;第一篇&#xff1a;概念、部署、架构&#xff09; Flink1.17实战教程&#xff08;第二篇&#xff1a;DataStream API&#xff09; Flink1.17实战教程&#xff08;第三篇&#xff1a;时间和窗口&#xff09; Flink1.17实战教程&…

mybatis快速批量插入工具类

代码示例&#xff1a; package com.ly.cloud.util; import java.util.List;import javax.annotation.PostConstruct; import javax.annotation.Resource;import com.google.common.collect.Lists; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.s…

C1189#error: WinSock.h has already been included解决方案

最近在做项目移植过程中遇到这个报错&#xff0c;解决了半天。简单记录下解决方案&#xff0c;以供给大家提供一个思路。 原因&#xff1a; 在工程中使用了Boot库之后&#xff0c;使用了socket、tcp相关的头文件&#xff0c;在其他地方还是包括了头文件<windows.h>&…

「Verilog学习笔记」超前进位加法器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 超前进位加法器的实质是&#xff1a;对于输出的每一位Si 其实都可以用Si Ai ^ Bi ^ Cin来表示 我们需要做的只是判断加法结果的最高位该取几 例如本题中 输入的两个数A和B…

云手机:多开群控全天在线,提高效率的最佳之选

对于需要高效处理多项任务的用户&#xff0c;Ogphone云手机以其多开、群控和全天在线的强大功能&#xff0c;成为提升效率的理想选择。下文将详细介绍Ogphone云手机的这三种功能是如何提高效率的。 多开分身&#xff1a;高效工作利器 Ogphone云手机采用基于ARM架构服务器的运行…

如何用电源模块综合测试系统测试逆变器电源输出电压?

万用表测量逆变器电源输出电压的方法 1.调整万用表到直流电压测量模式 2.确定测量电压的量程&#xff0c;选择合适挡位&#xff0c;一般建议选择稍大于逆变器额定电压的量程。 3.连接万用表“COM”插头到测量线的负极(通常为黑色插孔)&#xff0c;连接红色插头到测量线的正极(通…

【开源】基于JAVA的智能教学资源库系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 课程档案表3.2.2 课程资源表3.2.3 课程作业表3.2.4 课程评价表 四、系统展示五、核心代…

内网穿透的应用-开源表格工具APITable本地部署结合内网穿透实现公网访问

文章目录 前言1. 部署APITable2. cpolar的安装和注册3. 配置APITable公网访问地址4. 固定APITable公网地址 前言 vika维格表作为新一代数据生产力平台&#xff0c;是一款面向 API 的智能多维表格。它将复杂的可视化数据库、电子表格、实时在线协同、低代码开发技术四合为一&am…

Weblogic反序列化远程命令执行(CVE-2019-2725)

漏洞描述&#xff1a; CVE-2019-2725是一个Oracle weblogic反序列化远程命令执行漏洞&#xff0c;这个漏洞依旧是根据weblogic的xmldecoder反序列化漏洞&#xff0c;通过针对Oracle官网历年来的补丁构造payload来绕过。 复现过程&#xff1a; 1.访问ip&#xff1a;port 2.可…

数据结构学习 jz13衣橱整理

关键词&#xff1a;搜索算法 dfs bfs 回溯 题目&#xff1a; 各数位之和&#xff1a; 求法代码&#xff1a; int sums(int x){int s0;while(x!0){sx%10;xx/10;}return s;} 总的思路&#xff1a; 这道题是求可以到达的格子数&#xff0c;想到可以用搜索算法来做&#xff0c;…