【算法】单调队列

server/2024/9/24 10:22:23/

一、什么是单调队列

单调队列是一种数据结构,其特点是队列中的元素始终保持单调递增或递减,主要用于维护队列中的最小值或最大值

不同于普通队列只能从队头出队、队尾入队,单调队列为了维护其特征,还允许从队尾出队

不管怎么向单调队列中添加元素或删除元素,其单调性始终不变。这是如何做到的呢?我们用一道例题来说明

二、如何使用单调队列

2.1 滑动窗口问题

滑动窗口问题是单调队列的典型应用场景

简单来说,一个长度固定的窗口从序列开始一步步移动到结尾,我们要得到这个窗口每一步移动中其内部的最大值和最小值。

这个问题很简单,如果我们使用一个单调队列,窗口每次移动就将新元素入队,让其内部的元素保持单调递增,那么队头元素就是窗口的最小值,求最大值则保持单调递减即可

那我们该如何维护一个单调队列呢?首先来讲讲单调队列的思想

2.2 单调队列的思想

我们以上面例题中给出的序列 {1,3,-1,-3,5,3,6,7} 为例,窗口大小为3,单调队列大小和窗口一致

窗口从头开始向后移动,首先是1入队,然后是3入队,到这里单调队列内部都保持单调递增,于是我们不作处理

窗口继续向后移动,接下来是-1入队。但是-1入队后就打破了单调队列的单调性了,所以我们需要进行一些操作维护其单调性

因为我们选择保持队列单调递减,所以当有更小的元素要从队尾入队时,我们要把它前面所有比它更大的元素全都先从队尾出队

也就是说,当准备入队的元素更优时,我们需要先将前面造成干扰的元素出队,再将新元素入队。

此时,窗口已经完整的进入序列中了,可以开始拿到最值,此时单调队列的队头元素就是窗口中的最小值

窗口滑动,接下来-3准备入队。和前面的步骤一样,先将-1从队尾出队,然后-3入队

得到此时窗口最小值-3 

窗口滑动,接下来5正常入队

得到此时窗口最小值-3

窗口滑动,接下来3准备入队,和前面的步骤一样,先将5从队尾出队,然后3入队

得到此时窗口最小值-3

窗口滑动,接下来6正常入队

但是!此时单调队列中的-3已经滑出窗口范围了,需要出队

得到此时窗口最小值3

窗口滑动,接下来7正常入队

得到此时窗口最小值3

这就是单调队列完整的思想,如果要求窗口每个时刻的最大值,则将单调队列保持单调递减即可

关于单调队列还有一个很残酷的比喻:后入队的比先入队的年轻,如果后入队的既年轻又比先入队的更强,那先入队的就可以滚蛋了。就算先入队的更强,到了一定年龄之后也得滚蛋。

2.3 实际解题过程

明白了单调队列的思想后,我们还需要学会如何在实际解题时使用它

在上面的例题中,我们可以用一个数组模拟单调队列,用两个下标 h 和 t 来维护队头和队尾

另外一个数组存储目标序列,len 表示窗口长度,i 表示窗口的右侧,则 i - len + 1就是窗口的左侧。通过i++就可以实现窗口滑动的效果

我们在单调队列中存储元素在原序列中的下标,这样做是为了便于判断一个元素仍在单调队列中但已经滑出窗口的情况(如果该元素的下标小于窗口左侧则说明已经滑出窗口,则出队)

用下标维护队列头尾的目的:采用伪删除法,将 t-- 就能达到队尾出队的效果,h++就能达到队头出队的效果

大概思路都讲完了,这里直接放出例题的代码

#include <iostream>
using namespace std;const int N = 1000010;int n, k;
int a[N];
int q[N];void winmin() //求窗口最小值
{int h = 1, t = 0; //h是队头,t是队尾,队列初始为空for(int i = 1;i <= n;i++) //i为窗口右端,i递增则窗口不断滑动{while(h <= t && a[q[t]] >= a[i]) //队列不为空且队尾元素比新元素大,出队t--; q[++t] = i; //存储下标方便判断队头出队if(q[h] < i - k + 1) h++; //队头存储的下标小于窗口左侧,队头元素滑出窗口if(i >= k) cout << a[q[h]] << " "; //打印窗口最小值}cout << endl;
}void winmax() //求窗口最大值
{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) cout << a[q[h]] << " ";}cout << endl;
}int main()
{cin >> n >> k;for(int i = 1;i <= n;i++) //注意这里元素是从下标为1处开始存储cin >> a[i];winmin();winmax();return 0;
}

完.


http://www.ppmy.cn/server/62243.html

相关文章

Android 14 开机时间优化措施

Android 14 开机时间优化措施 在Android 14中&#xff0c;优化开机时间涉及多个层级的性能优化&#xff0c;从系统启动到应用加载的每一个阶段都可能影响最终的开机时间。以下是详细的措施和策略&#xff0c;可以帮助我们在Android 14设备上进行开机时间优化。 1. 优化引导过…

Python打开Excel文档并读取数据

Python 版本 目前 Python 3 版本为主流版本&#xff0c;这里测试的版本是&#xff1a;Python 3.10.5。 常用库说明 Python 操作 Excel 的常用库有&#xff1a;xlrd、xlwt、xlutils、openpyxl、pandas。这里主要说明下 Excel 文档 .xls 格式和 .xlsx 格式的文档打开和读取。 …

JavaSE 面向对象程序设计进阶 IO 压缩流 解压缩流

目录 解压缩流 压缩流 解压缩流 压缩包 压缩包里面的每一个文件在java中都是一个ZipEntry对象 把每一个ZipEntry按照层级拷贝到另一个文件夹当中 import java.io.*; import java.util.Date; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream;public cl…

ArcGIS Pro SDK (八)地理数据库 8 拓扑

ArcGIS Pro SDK &#xff08;八&#xff09;地理数据库 8 拓扑 文章目录 ArcGIS Pro SDK &#xff08;八&#xff09;地理数据库 8 拓扑1 开放拓扑和进程定义2 获取拓扑规则3 验证拓扑4 获取拓扑错误5 标记和不标记为错误6 探索拓扑图7 找到最近的元素 环境&#xff1a;Visual …

Spring Boot 3.3 【二】Spring Boot自动配置机制深度解析

简单动作&#xff0c;深刻联结。在这技术海洋&#xff0c;我备好舟&#xff0c;等你扬帆。启航吧&#xff01; &#x1f31f;点击【关注】&#xff0c;解锁定期的技术惊喜&#xff0c;让灵感与知识的源泉不断涌动。 &#x1f44d;一个【点赞】&#xff0c;如同心照不宣的默契&a…

PostgreSQL安装/卸载(CentOS、Windows)

说明&#xff1a;PostgreSQL与MySQL一样&#xff0c;是一款开源免费的数据库技术&#xff0c;官方口号&#xff1a;The World’s Most Advanced Open Source Relational Database.&#xff08;世界上最先进的开源关系数据库&#xff09;&#xff0c;本文介绍如何在Windows、Cen…

打造你的智能家居指挥中心:基于STM32的多协议(zigbee、http)网关(附代码示例)

1. 项目概述 随着物联网技术的蓬勃发展&#xff0c;智能家居正逐步融入人们的日常生活。然而&#xff0c;市面上琳琅满目的智能家居设备通常采用不同的通信协议&#xff0c;导致不同品牌设备之间难以实现互联互通。为了解决这一难题&#xff0c;本文设计了一种基于STM32的多协…

VIM模式之间的切换

命令行界面下&#xff0c;常用的文本编辑器是 VI / VIM(VI增强版)&#xff0c;VI 是 Linux 最通用的文本编辑器&#xff0c;VIM相较于VI&#xff0c;提供了代码高亮等功能&#xff0c;两者用法完全兼容&#xff1b; 1. 进入 VIM 工作界面 vim 文件名 2. 进入编辑模式 三种方…