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

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

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

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

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

首先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,即符合条件的连续子序列的最大长度。

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

感谢观看!

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

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

相关文章

Spring之整合Mybatis底层源码

文章目录 一、整体核心思路1 . 简介2. 整合思路 二、源码分析1. 环境准备2. 源码分析 一、整体核心思路 1 . 简介 有很多框架需要与Spring进行整合&#xff0c;而整合的核心思路就是把其他框架所产生的对象放到Spring容器中&#xff0c;让其成为一个bean。比如Mybatis&#x…

使用requests库测试post请求 操作流程

第一步 谷歌f12或其他抓包工具抓包&#xff0c;这里随机抓一个post请求 url&#xff1a;https://eva2.csdn.net/v3/06981375190026432f77c01bfca33e32/lts/groups/dadde766-b087-42da-8e67-d2499a520ee7/streams/a0119567-bf91-4314-ab75-f683ba6c0c0a/logs 第二步 导包 impo…

uniapp在web端怎么使用svg图标呢

在图标库中添加好项目用到的图标&#xff0c;点击symbol点击生成在线链接 点击生成的在线链接&#xff0c;此时会跳转到一个新窗口&#xff0c;是一个js文件 复制这个js文件的内容 然后在uniapp中新建svg.js文件&#xff0c;把从上面复制的代码粘贴到这个svg.js中 在main.js中引…

在本地测试nginx中localhost不行,需要写成127.0.0.1

在Windows 10系统的命令提示符cmd中&#xff0c;执行命令ping localhost&#xff0c;并没有出现我与其的ip地址“127.0.0.1”&#xff0c;而是“[::1]”。 问题原因 在cmd中ping localhost解析出来的是ipv6的::1的原因是windows有个优先解析列表&#xff0c;当ipv6的优先级高于…

C++学习笔记——对象的指针

目录 一、对象的指针 二、减少对象的复制开销 三、应用案例 游戏引擎 图像处理库 数据库管理系统 航空航天软件 金融交易系统 四、代码的案例应用 一、对象的指针 是一种常用的技术&#xff0c;用于处理对象的动态分配和管理。使用对象的指针可以实现以下几个方面的功…

pythroch abaconda 安装 cuda、版本确定、pytorch 安装

一、简述 公司有一个深度学习的项目&#xff0c;身上也没有其他项目&#xff0c;恰好乘着个机会学一下pytorch 和YOLOv8. 1、下载abaconda https://repo.anaconda.com/archive/ 2、安装 环境变量要✔ 其他一直下一步 3、测试 (base) C:\Users\alber>conda -V cond…

Tensorflow Lite从入门到精通

TensorFlow Lite 是 TensorFlow 在移动和 IoT 等边缘设备端的解决方案&#xff0c;提供了 Java、Python 和 C API 库&#xff0c;可以运行在 Android、iOS 和 Raspberry Pi 等设备上。目前 TFLite 只提供了推理功能&#xff0c;在服务器端进行训练后&#xff0c;经过如下简单处…

C++11_lambda表达式

文章目录 一、lambda表达式1.lambda的组成2.[capture-list] 的其他使用方法2.1混合捕捉 二、lambda表达式的使用场景1.替代仿函数 总结 一、lambda表达式 lambda表达式是C11新引入的功能&#xff0c;它的用法与我们之前学过的C语法有些不同。 1.lambda的组成 [capture-list] …

消息开始事件message start event

一&#xff1a;bpmn 二&#xff1a;java repositoryService.createDeployment().name("消息事件流程").addClasspathResource("bpmn/msg_event_process.bpmn").deploy(); identityService.setAuthenticatedUserId("huihui"); ProcessInstance p…

vue3中ref和reactive联系与区别以及如何选择

vue3中ref和reactive区别与联系 区别 1、ref既可定义基本数据类型&#xff0c;也可以定义引用数据类型&#xff0c;reactive只能定义应用数据类型 2、ref在js中取响应值需要使用 .value&#xff0c;而reactive则直接取用既可 3、ref定义的对象通过.value重新分配新对象时依旧…

放电深度对电池寿命的影响

一、SOC和DOD概念 SOC&#xff1a;State Of Charge. 电池SOC是指荷电状态&#xff0c;是用来反映电池的剩余容量的&#xff0c;其数值上定义为剩余容量占电池容量的比值&#xff0c;常用百分数表示&#xff1b;其取值范围为0~1&#xff0c;当“SOC0”时表示电池放电完全&#…

python爬虫实战(6)--获取某度热榜

1. 项目描述 需要用到的类库 pip install requests pip install beautifulsoup4 pip install pandas pip install openpyxl然后&#xff0c;我们来编写python脚本&#xff0c;并引入需要的库&#xff1a; import requests from bs4 import BeautifulSoup import pandas as p…

SpringBoot 自动装配原理

一、什么是自动装配 自动装配是指应用程序启动时由容器自动扫描和装配相关的组件和模块&#xff0c;无须像传统的spring那样在xml文件中手动配置Bean&#xff0c;从而简化了应用程序的配置过程&#xff0c;提高开发效率。 二、SpringBootApplication解析 进入到这个 SpringB…

概率中的 50 个具有挑战性的问题 [9/50]:掷骰子

一、说明 我最近对与概率有关的问题产生了兴趣。我偶然读到了弗雷德里克莫斯特勒&#xff08;Frederick Mosteller&#xff09;的《概率论中的五十个具有挑战性的问题与解决方案》&#xff09;一书。我认为创建一个系列来讨论这些可能作为面试问题出现的迷人问题会很有趣。每篇…

你需要尽早知道的15个开源网络安全工具

在本文中&#xff0c;你将找到一个开放源码网络安全工具列表&#xff0c;你一定要查看这些工具。 开源工具代表了技术版图中的一股有活力的力量&#xff0c;体现了创新、协作和可访问性&#xff0c;这些工具是根据透明度和社区驱动的原则开发的&#xff0c;使用户能够根据其独特…

Vue-13、Vue深度监视

1、监视多级结构中某个属性的变化 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>watch深度监视</title><script type"text/javascript" src"https://cdn.jsdelivr.net/npm…

Vue基础-搭建Vue运行环境

这篇文章介绍了在Vue.js项目中进行开发环境搭建的关键步骤。包括node.js安装和配置、安装Vue及Vue CLI工具、安装webpack模板、安装vue-router、创建Vue项目等步骤。这篇文章为读者提供了清晰的指南&#xff0c;帮助他们快速搭建Vue.js开发环境&#xff0c;为后续的项目开发奠定…

端到端自动驾驶

自动驾驶主要流程&#xff1a;感知->预测->规划 预测是预测周围目标&#xff08;车、行人、动物等&#xff09;的轨迹&#xff0c;规划是规划自车的运动轨迹。 UniAD[CVPR 2023]: 使用transformer架构&#xff0c;统一自动驾驶流程&#xff0c;完成所有检测&#xff0c…

如何上传苹果ipa安装包?

目录 引言 摘要 第二步&#xff1a;打开appuploader工具 第二步&#xff1a;打开appuploader工具&#xff0c;第二步&#xff1a;打开appuploader工具 第五步&#xff1a;交付应用程序&#xff0c;在iTunes Connect中查看应用程序 总结 引言 在将应用程序上架到苹果应用…

Navicat 技术干货 | 为 MySQL 表选择合适的存储引擎

MySQL 是最受欢迎的关系型数据库管理系统之一&#xff0c;提供了不同的存储引擎&#xff0c;每种存储引擎都旨在满足特定的需求和用例。在优化数据库和确保数据完整性方面&#xff0c;选择合适的存储引擎是至关重要的。今天&#xff0c;我们将探讨为 MySQL 表选择合适的存储引擎…