数据结构之单调栈、单调队列

news2024/11/17 1:50:00

今天学习了单调栈还有单调队列的概念和使用,接下来我将对其定义并配合几道习题进行讲解:

首先先来复习一下栈与队列:

然后我们来看一下单调栈的定义:
单调栈中的元素从栈底到栈顶的元素的大小是按照单调递增或者单调递减的关系进行排列的,由于它的这个性质可以方便我们解决很多问题,接下来看一道可以用这个单调栈来解决的例题:

接下来我会提供两个不同的代码 但其实本质一样的解答,我们可以用样例模拟一下过程,这里我们考虑用一个单调递减的栈进行解答:

首先2进栈,然后接下来是6进栈,首先我们判断出6比2大,那么2对应的答案就是6的下标2并将2出栈,接下来3 1依次进栈,他们两个都满足递减,接下来5进栈之后,5对应的下标就是3 1两个的答案,并将这两个出栈,这样一直模拟下去就会得到每一个的答案,接下来上代码(从左往右看):

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int a[N],top,ans[N],n,s[N];
int main(){
	//先完成所有的输入
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	//这里就是本题的模拟过程
	for(int i=1;i<=n;i++){
		//我们这个时候栈即将进来一个元素,这时候我们将这个元素与单调递减栈中元素从上往下进行比较
		while(top && a[i]>a[s[top]]){
			//如果满足while循环条件,也就是即将进站元素大于栈顶元素,那么栈顶元素对应的答案下标就是即将进栈元素的下标
		ans[s[top]]=i;
		//将栈顶元素出栈
		--top;
		}
		//将这个元素进栈
		s[++top]=i;
	}
	//最后由于会有后面不存在比他更大的元素,这时候把他们都设置为0
	for(int i=1;i<=top;i++)
	ans[s[i]]=0;
	//逐个输出
	for(int i=1;i<=n;i++)
	cout<<ans[i]<<' ';
	return 0;
}

上面的代码我附着了详细的讲解 

这里其实也可以从右往左进行枚举并同样使用单调栈进行解答,代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int n,top,a[N],s[N],ans[N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	//从右往左进行模拟
	for(int i=n;i;i--){
		//当栈非空并且栈顶元素是小于即将进栈的元素时将栈顶元素去除
		while(top && a[s[top]]<=a[i])
		--top;
		//如果栈非空,则说明这时候栈顶元素就是大于此时即将进栈元素的第一个元素
		if(top) ans[i]=s[top];
		else ans[i]=0;//否则就没有比他更大的数
		s[++top]=i;//入栈
	}
	//逐个输出
	for(int i=1;i<=n;i++)
	cout<<ans[i]<<' ';
	return 0;
}

接下来看第二道题目:

最大矩形面积:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int a[N],top,l[N],r[N],n,s[N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	//从左往右计算每个位置左边第一个比他矮的
	for(int i=1;i<=n;i++){
	   while(top && a[i]<=a[s[top]])
	   --top;
	   if(top) l[i]=s[top];
	   else l[i]=0;
	   s[++top]=i;
	}
	//清空栈
	top=0;
	//从右往左模拟计算每个数字右边第一个高度小于它的位置
	for(int i=n;i;i--){
	if(top && a[i]<=a[s[top]])	
	   --top;
	   if(top) r[i]=s[top];
	   else r[i]=n+1;
	   //入栈
	   s[++top]=i;
	}
	long long ans=0;
	//计算最大的矩形面积
	for(int i=1;i<=n;i++)
	ans=max(ans,1LL*a[i]*(r[i]-l[i]-1));
	cout<<ans<<endl;
	return 0;
}

第三题的难度较大:数对统计

接下来我会给出代码并附着具体的思路以及分析:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int n;
int a[N],s[N],top,ans;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	/*上面是输入部分*/
	/*接下来我们分析一下题目 题目共有n个不同的数字,我们要求出有多少个数对i,j在i,j中之间的元素不存在大于边界的元素的数对个数
	我们可以分析首先挨着的两个数字都能达到这样的条件,因为他俩之间一个数字都没有
	然后我们考虑当有三个及以上数字的数对的时候,中间的所有元素都不能大于两边,这里就是我们运用单调栈解答的关键思路*/
	for(int i=1;i<=n;i++){
		/*下标为i的元素即将进栈*/
		while(top && a[i]>=s[top]){
		/*将即将进栈的元素与栈顶元素作大小的对比
		如果大于栈顶元素,那么以栈顶元素开始的数对i,满足条件的j的数对的终点最长也就是此时即将进栈的元素,因此移除栈顶元素并让答案加一*/
			--top;
			++ans;
		}
		if(top) ++ans;//这里如果栈顶还有元素的话,说明这个栈顶的元素是大于即将进栈的元素的,那么他们之间的所有元素与以栈顶元素还有即将进栈的元素组成的数对满足条件
		s[++top]=a[i];
	/*上述for循环中 我们考虑的是运用一个单调栈来模拟一个答案数量的增加过程*/
	cout<<ans<<endl;//输出答案
	return 0; 
}

接下来看单调队列:

定义:队列中的元素按照递增或者递减的线性关系排列的队列。

利用队列先进先出的特点以及单调队列的特质可以用来解决很多的问题,接下来看例题:

1.动态区间的最大数:

这个题目我们考虑用一个单调递减的队列进行解答:

我们可以维护队首元素是最大的数字,他就是每个m长度区间的答案,然而他最多只可能连续被输出m次,因为无论多大,队列的长度最大同时只能是m,总的来说我们需考虑下面三个问题:

 

 接下来上代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int n,m;
int a[N],q[N],front=1,rear=0;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	/*接下来是代码的精华部分*/
	for(int i=1;i<=n;i++){
	while(front<=rear && a[q[rear]]<=a[i])//当队列非空并且即将入队的元素是大于队尾元素的时候
	--rear;	//由于要维护一个单调递减的队列,所以这个时候需要将队尾元素暂时性出队
	q[++rear]=i;//将对应的元素下标入队
	/*这里想说明一点就是当前子队列的下标是从front开始到i的,虽然中间会更换队尾的元素以便于维护单调队列的单调性,但是右边界始终就是i*/
	if(m<i-q[front]+1) ++front;//当队列的长度大于m的时候,将队首元素出队,这时候的最大值应该是后面队列中进行挑选了
	if(i>=m) cout<<a[q[front]]<<' ';//输出每个对应的动态区间的最大值
	}
	return 0;
}

接下来看这道题的模板:

 接下来看第二道例题:

接下来附上代码以及讲解:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int n,m,a[N],s[N],q[N],front=1,rear=0,l,r;
int main(){
	cin>>n>>l>>r;
	s[0]=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	// 上面完成所有的输入并且利用s数组来计算出所有的前缀和
	int x=l,ans=-1<<30;//将x的长度初始值设置成为最短的区间长度l并且由于a数组中的元素可能都为负数那么我们一开始的默认ans需要设置的很小以便于应付极端情况这里的x其实也就是区间的右端点
	//接下来从1开始进行枚举并通过维护一个单调递减的队列,其中存储的是前缀和数组的下标
	for(int i=1;i+l-1<=n;i++){//请注意这里的i其实是区间和的左端点,队列中我们存储的都是前缀和数组的下标
		while(x<=i+r-1 && x<=n){//这个x是用来维护一个长度为l到r的并且小于数组长度n的一个区间长度
			while(front <= rear && s[q[rear]]<=s[x])//当队列非空的时候为了维护一个单调递减的前缀和区间队列,进行队列的更新
			--rear;
			q[++rear]=x;//将x插入到队尾
			++x;//并且将x的长度加一
		}
		if(q[front]<i+l-1)//当这时候队首对应的前缀和区间长度不足l的时候,将队首出队
		++front;
		ans=max(ans,s[q[front]]-s[i-1]);//更新最大的ans
	}
	cout<<ans<<endl;
	return 0; 
}

这一道题目需要仔细的理解单调队列在其中的运用,请读者仔细领悟与思考。

接下来看最后一道题目,覆盖:

接下来请看代码:

#include <bits/stdc++.h>

using namespace std;

int n, h, a[200001], q1[200001], front1 = 1, rear1 = 0, q2[200001], front2 = 1, rear2 = 0; 

int main() {
	scanf("%d%d", &n, &h);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	int j = 0, ans = 0;
	for (int i = 1; i <= n; i++) {
		if (front1 <= rear1 && q1[front1] < i)
			++front1;
		if (front2 <= rear2 && q2[front2] < i)
			++front2;
		while (j <= n && (j <= i || a[q1[front1]] - a[q2[front2]] <= h)) {
			++j;
			if (j > n)
				break;
			while (front1 <= rear1 && a[q1[rear1]] <= a[j])
				--rear1;
			q1[++rear1] = j;
			while (front2 <= rear2 && a[q2[rear2]] >= a[j])
				--rear2;
			q2[++rear2] = j;
		}
		ans = max(ans, j - i);
	}
	printf("%d\n", ans);
}

  1. 数组初始化

    • a[200001]:存储输入的 n 个数字。
  2. 两个单调队列

    • q1:维护最大值的单调递减队列。
    • q2:维护最小值的单调递增队列。
    • front1rear1front2rear2:队列的头尾指针。
  3. 主要逻辑

    • 通过双指针 ij 遍历数组。
    • 对于每个位置 i,在内循环中找到满足条件的 j,使得子序列中最大值和最小值的差值不超过 h
    • j 的移动过程中,更新两个单调队列 q1q2
    • 计算并更新最大长度 ans
  4. 内循环

    • j 的循环中,不断尝试扩展子序列的右边界 j,直到满足条件:a[q1[front1]] - a[q2[front2]] <= h 或者超出数组范围。
    • 更新两个队列 q1q2 以维护最大值和最小值的索引。
  5. 最终结果

    • 输出得到的最大长度 ans,即符合条件的连续子序列的最大长度。

这段代码使用了两个单调队列来记录最大值和最小值的索引,在滑动窗口的过程中寻找满足条件的子序列,并记录其长度。

感谢观看!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

九州金榜|厌学原因孩子情绪不稳定

孩子厌学是每个家长都不愿因看到&#xff0c;因为厌学会对孩子学习造成极大的影响&#xff0c;对于学习成绩下降这是必然的结果&#xff0c;所以&#xff0c;当孩子出现厌学情绪的时候&#xff0c;家长就会非常焦虑&#xff0c;但是对于孩子为什么会厌学&#xff0c;家长并不知…

烟火检测/区域人流统计/AI智能分析网关V4如何配置通道?

TSINGSEE青犀智能分析网关&#xff08;V4版&#xff09;是一款高性能、低功耗的软硬一体AI边缘计算硬件设备&#xff0c;硬件内部署了近40种AI算法模型&#xff0c;支持对接入的视频图像进行人、车、物、行为等实时检测分析&#xff0c;并上报识别结果&#xff0c;并能进行语音…

java SSM问卷调查系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM问卷调查管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

【Vue】文件管理页面制作

<template><div><div style"margin: 10px 0"><el-input style"width: 200px" placeholder"请输入名称" suffix-icon"el-icon-search" v-model"name"></el-input><el-button class"ml…

Logo设计神器:适合新手的简易操作软件,快速入门!

标志设计软件在品牌营销和企业识别中发挥着重要作用。本文将对10款知名标志设计软件进行横向评价&#xff0c;从不同维度评价其功能、易用性、创意和适用性&#xff0c;帮助您选择最适合您需求的标志设计软件。 1.即时设计 推荐指数&#xff1a;★★★★★ 即时设计是一款功…

【LeetCode】winter vacation training

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb; 有效的字母异位词&#x…

超维空间M1无人机使用说明书——53、ROS无人机二维码识别与降落——V2升级版本

引言&#xff1a;使用二维码引导无人机实现精准降落&#xff0c;首先需要实现对二维码的识别和定位&#xff0c;可以参考博客的二维码识别和定位内容。本小节主要是通过获取拿到的二维码位置&#xff0c;控制无人机全向的移动和降落&#xff0c;本小节再V1版本的基础上增加了动…

软件测试|MySQL HAVING分组筛选详解

简介 在 MySQL 数据库中&#xff0c;HAVING 子句用于在使用 GROUP BY 子句对结果进行分组后&#xff0c;对分组后的数据进行筛选和过滤。它允许我们对分组后的结果应用聚合函数&#xff0c;并基于聚合函数的结果进行条件过滤&#xff0c;从而得到我们需要的最终结果集。本文将…

Ubuntu 22.0.4 忘记重置 MySQL 密码

Ubuntu 22.0.4 忘记重置 MySQL 密码 一、问题描述二、解决办法 一、问题描述 Ubuntu 22.0.4 忘记了 MySQL的密码&#xff0c;需要重新设置密码 环境描述&#xff1a; 系统&#xff1a;Ubuntu 22.0.4 MySQL&#xff1a;8.0.35 &#xff08;通过 apt install mysql-sever 安装的…

0110qt

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示"登录成功"&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

注意力机制简单理解

1. 什么是注意力机制&#xff1f; ​ 我们在日常的生活中对许多事物都有我们自己的注意力重点&#xff0c;通过注意力我们可以更加关注于我们注意的东西&#xff0c;从而过滤不太关注的信息。 看到一张人像图时&#xff0c;我们会更关注人的脸部&#xff0c;其次根据脸部再细分…

VScode 画图插件

开源免费的插件 随着http://draw.io开源vs code插件之后&#xff0c;它一跃成为最强大的流程图工具。 目前http://draw.io支持3种文件后缀&#xff0c;你只需要新建3种后缀之一的文件就可以在vs code中画流程图&#xff0c;它们分别是&#xff1a; *.drawio*.dio*.drawio.sv…

使用KubeSphere轻松部署Bookinfo应用

Bookinfo 应用 这个示例部署了一个用于演示多种 Istio 特性的应用&#xff0c;该应用由四个单独的微服务构成。 如安装了 Istio&#xff0c;说明已安装 Bookinfo。 这个应用模仿在线书店的一个分类&#xff0c;显示一本书的信息。 页面上会显示一本书的描述&#xff0c;书籍…

【JVM 基础】类字节码详解

JVM 基础 - 类字节码详解 多语言编译为字节码在JVM运行Java字节码文件Class文件的结构属性从一个例子开始反编译字节码文件字节码文件信息常量池方法表集合类名 再看两个示例分析try-catch-finallykotlin 函数扩展的实现 源代码通过编译器编译为字节码&#xff0c;再通过类加载…

06.构建大型语言模型步骤

在本章中,我们为理解LLMs奠定了基础。在本书的其余部分,我们将从头开始编写一个代码。我们将以 GPT 背后的基本思想为蓝图,分三个阶段解决这个问题,如图 1.9 所示。 图 1.9 本书中介绍的构建LLMs阶段包括实现LLM架构和数据准备过程、预训练以创建基础模型,以及微调基础模…

设计模式—行为型模式之策略模式

设计模式—行为型模式之策略模式 策略&#xff08;Strategy&#xff09;模式定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换&#xff0c;且算法的变化不会影响使用算法的客户。属于对象行为模式。 策略模式的主要角色如下。 抽象策略&…

uni-app分包预下载

模块的二级页面&#xff0c;按模块处理成分包页面&#xff0c;有以下好处&#xff1a; 按模块管理页面&#xff0c;方便项目维护。减少主包体积&#xff0c;用到的时候再加载分包&#xff0c;属于性能优化解决方案。 ::: tip 温馨提示 通过 VS Code 插件 uni-create-view 可…

对root用户的理解

1.什么是root用户&#xff1f; Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。在Linux系统中&#xff0c;拥有最大权限的账户名为&#xff1a;root&#xff08;超级管理员&#xff09; root用户拥有最大的系统操作权限&#xff0c;而普通用户在许多地方的权限是受…

圣诞老人遇见 GenAI:利用大语言模型、LangChain 和 Elasticsearch 破译手写的圣诞信件

在北极的中心地带&#xff0c;圣诞老人的精灵团队面临着巨大的后勤挑战&#xff1a;如何处理来自世界各地儿童的数百万封信件。 圣诞老人表情坚定&#xff0c;他决定是时候将人工智能纳入圣诞节行动了。 圣诞老人坐在配备了最新人工智能技术的电脑前&#xff0c;开始在 Jupyter…

大气精美网站APP官网HTML源码

源码介绍 大气精美网站APP官网源码&#xff0c;好看实用&#xff0c;记事本修改里面的内容即可&#xff0c;喜欢的朋友可以拿去研究 下载地址 蓝奏云&#xff1a;https://wfr.lanzout.com/itqxN1ko2ovi CSDN免积分下载&#xff1a;https://download.csdn.net/download/huayu…