算法通关--单调栈

news2024/11/30 18:53:26

单调栈

单调栈是在栈的先进后出的规则基础上,要求从栈底到栈顶的元素满足单调的关系。如果是大压小,那么从栈顶观察是递减栈,如果是小压大,那么从栈顶观察使递减栈

经典用法:

判断一个数组每个位置都求:
1.当前位置的 左侧比当前位置的数字小(大),且距离最近的位置在哪
2.当前位置的 右侧比当前位置的数字小(大),且距离最近的位置在哪

如果按照暴力求解,那么时间复杂度来到了O(N^2),但是对于单调栈来说,时间复杂度为O(N)

1.数组无重复值的情况

下面有数组[2,5,6,1,4,7,5],找出数组中比当前位置数字小的左右的位置。我们建立一个栈,栈内的元素服从数值大压小的规则,并且栈中放的是下标,因为可以通过下标来找到数组中对应位置元素的值。注:0-->2表示的是下标0位置的元素2

0-->2进栈,1-->5进栈,2-->6进栈,1-->4准备进栈,发现栈顶元素6比自己大,那么元素6出栈,元素6压的是元素5,因此元素6左边离自己最近且小于自己的位置是1-->5,3-->1使自己出去,那么右边离自己最近且小于自己的位置是3-->1,如图:

 但是2-->6出栈后,3-->1可以进栈吗,不能!!!因为1-->5比1大,0-->2比1大,当它们两个出栈后,3-->1就可以进栈了,此时可以得到:

2-->61-->53-->1
1-->50-->23-->1
0-->2-13-->1

之后4-->4进栈,5-->7进栈,6-->5进栈前弹出5-->7,最终得到栈中的元素以及列表如下:左边为-1,表示为栈底元素,在数组中的表现是左边没有比自己元素小的数字

2-->61-->53-->1
1-->50-->23-->1
0-->2-13-->1
5-->74-->46-->5

 数组中没有可以压栈的数字了,进入清算阶段,清算阶段栈中的右边均为-1,(没有元素使自己弹出),右边为自己压的元素。那么最终形成的列表为:

当前位置
2-->61-->53-->1
1-->50-->23-->1
0-->2-13-->1
5-->74-->46-->5
6-->54-->4-1
4-->43-->1-1
3-->1-1-1

 2.数组有重复值的情况

当遇到有重复值的情况,对于重复的数值先进栈的元素进行弹栈,后进栈的元素进行压栈,对于进行弹栈的元素记录左右位置的元素,即使右边的元素可能不对,但是在最后的阶段进行修正。

例如:数组[2,4,3,5,2,6],求每个位置的数字离自己最近的最小值元素,按照上面的操作,最终得到以下列表

当前位置
1-->40-->22-->3
3-->52-->34-->2
2-->30-->24-->2
0-->2-14-->2
5-->64-->2-1
4-->2-1-1

比较当前位置的元素和表格中右边位置的元素,如果比自己小,那么没有问题,如果和自己相等,那么取表格右边位置的下标的右边的值。例如:0-->2的右边是4-->2,这个有问题,那么0-->2右边是4位置右边填的数值-1。由于栈中的元素是大压小,因此左侧求出的元素没有问题

例题1:

单调栈结构(进阶)_牛客题霸_牛客网

我们利用数组进行栈的实现,其中r表示栈中的剩余的元素,ans[][]表示答案的二维数组:代码如下:

#include <iostream>
using namespace std;
const int MAXN = 1000001;

int arr[MAXN];
int ans[MAXN][2];
int n, r;
// 数组模拟实现栈
void compute() {
	int stack[MAXN];
	r = 0;        // 表示栈的状态
	int cur;      // 答案数组的指针

	for (int i = 0; i < n; i++) {
		// 构建一个大压小的单调栈,如果栈不为空,且不满足大压小,进行while循环(弹栈)
		while (r > 0 && arr[stack[r - 1]] >= arr[i]) {
			cur = stack[--r];
			// cur当前弹出的位置,左边是栈顶元素,右边是让自己弹出位置的元素
			ans[cur][0] = r > 0 ? stack[r - 1] : -1;
			ans[cur][1] = i;
		}
		// 要么栈为空 要么是 arr[i] > 栈顶元素,进栈
		stack[r++] = i;
	}
	// 清算阶段 -- 栈里还有剩余
	while (r > 0) {
		cur = stack[--r];
		ans[cur][0] = r > 0 ? stack[r - 1] : -1;
		ans[cur][1] = -1;
	}
	// 修正阶段 -- 数组中有重复元素
	// 左侧的答案不需要修正一定是正确的,只有右侧答案需要修正
	// 从右往左修正,n-1位置的右侧答案一定是-1,不需要修正
	for (int i = n - 2; i >= 0; i--) {
		if (ans[i][1] != -1 && arr[ans[i][1]] == arr[i]) {
			ans[i][1] = ans[ans[i][1]][1];
		}
	}
}

int main() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> arr[i];
	}
	compute();
	for (int i = 0; i < n; i++) {
		cout << ans[i][0] << " " << ans[i][1] << endl;
	}
	return 0;
}

例题2:

https://leetcode.cn/problems/daily-temperatures/

对于temperatures=[73,74,75,71,69,72,76,73]来说,如果建立大压小的栈,那么求出每一个位置的值使小于当前位置的天数,与题意要求的不符,因此建立小压大的栈,使当前位置弹出的位置减去当前位置即为答案,在清算阶段,直接为0。例如,1位置的74使0位置的73弹出,那么答案数组中的ans[0]就是1.代码如下

vector<int> dailyTemperatures(vector<int>& temperatures) {
	int n = temperatures.size();
	vector<int> ans(n);
	stack<int> s;
    // 构建小压大的栈,栈中记录的是下标!!!
	for (int i = 0; i < n; i++) {
    
		while (!s.empty() && temperatures[i] > temperatures[s.top()]) { // 不为空且不能大压小
			int top = s.top();    
			ans[top] = i - top;	// 记录应该放那个数字,哪个位置的比他大
			s.pop();    // 弹出
		}
        // 为空or小压大
		s.push(i);
	}
    // 数组初始化时每个位置为0,因此直接返回
	return ans;
}

例题3:

907. 子数组的最小值之和 - 力扣(LeetCode)

如果是分别求出每一个子数组,在从子数组中挑选最小值相加的话,太慢了,利用单调栈怎么解?
既然是子数组,那就是一个区间,求一个区间的最小值,一个区间往外扩,未扩充之前的最小值一定大于等于扩充之后的最小值。那么在一个区间内的最小值,它的区间左边界的左边的值一定小于等于当前区间的最小值,它的区间右边界的右边的值一定小于等于当前区间的最小值,例如:
2|9 7 6 8|1,对于| |区间的最小值6来说,2和1均比6小。那么这个和单调栈有什么关系?
我们继续看:只要过在这个数组区间内,过6的子区间,最小值一定是6 ,即[9,7,6],[9,7,6,8],[7,6],[7,6,8],[6],[6,8]这些子区间都是以6为最小值,那么这些子区间的最小值为6*6=36;这不就求出和了吗,6这个区间长度怎么快速得到,就是单调栈,(当前位置的下标值-它的左边)*(它的右边当前位置的下标值),即(3-0)*(5-3) = 6;

const int MOD = 1000000007;
const int MAXN = 30001;
int stack[MAXN];
int r;

int sumSubarrayMins(vector<int>& arr) {
    long long ans = 0;
    r = 0;
    for (int i = 0; i < arr.size(); i++) {
        while (r > 0 && arr[stack[r - 1]] >= arr[i]) {
            int cur = stack[--r];
            int left = r == 0 ? -1 : stack[r - 1];
            ans = (ans + (long long)(cur - left) * (i - cur) * arr[cur]) % MOD;
        }
        stack[r++] = i;
    }
    while (r > 0) {
        int cur = stack[--r];
        int left = r == 0 ? -1 : stack[r - 1];
        ans = (ans + (long long)(cur - left) * (arr.size() - cur) * arr[cur]) % MOD;
    }
    return (int)ans;
}

例题4:

84. 柱状图中最大的矩形 - 力扣(LeetCode)

这个题怎么看,找以每个位置为高度,求可以拼成的最大矩形,左边对于比自己高的,可以扩充,比自己低的不可以扩充,那么这个就是求得每一个位置左比自己小的数,例如[2,1,5,6,2,3],对于0位置的2来说,左边没有,扩大不了,右边比自己小,扩不了,那么面积就是2;对于1位置的1来说,左边扩1个,右边扩4个,面积是6*1;对于2位置的5来说,左边不能扩,右边扩1个,面积为2*5=10;对于3位置的6来说,左边不能扩,右边不能扩,面积1*6,对于4位置的2来说,左边扩2个,右边扩1个,面积为4*2=8,对于5位置的3来说,左边不能扩,右边不能扩,面积为3;最终的整体为10.还是根据单调栈的模板,求得左边和右边比自己小值的位置
注意区间的长度,当地位置为i,由于是减小就往左扩,当while循环进不去的时候,左边的下标不在“可扩增区间内”,例如[2,1,5,6,2,3],当i在5位置的2时,i==4,对数值6得出left==2,因为此时3位置的6已经弹栈,那么获取的top就是2位置,i-left-1==1,符合条件。注意:弹出谁是才能算出该位置的矩形面积,那么最终代码是:

int largestRectangleArea(vector<int>& heights) {
	int ans = 0;
	stack<int> s;
    n = heights.size();
	for (int i = 0; i < heights.size(); i++) {
		while (!s.empty() && heights[i] < heights[s.top()]) {
			int cur = s.top();
			s.pop();
			int left = s.empty() ? -1 : s.top();
			ans = max(ans, (i - left - 1) * heights[cur]);    // 算面积!!!一定注意!!!
		}
		s.push(i);
	}
	while (!s.empty()) {
		int cur = s.top();
		s.pop();
		int left = s.empty() ? -1 : s.top();
		ans = max(ans, (n - left - 1) * heights[cur]);
	}
	return ans;
}

例题5:

962. 最大宽度坡 - 力扣(LeetCode)

题目要求的满足条件i<j并且arr[i]<arr[j],求j-i的最大值,首先构建一个严格小压大的栈(不进行弹栈,只压栈),严格小压大就是如果遇到和栈顶相同的数值,不压栈,这样子做的好处是如果后面的数值比栈顶大,那么下标减去先压栈的下标一定大于下标减去后压栈的下标,(一句话,先压栈求的结果更大,更符合题意)。
构建完后,从数组右边开始,比栈顶元素大,计算距离,弹出栈顶元素,(这是因为如果不弹出,下一个比栈顶大的算的距离一定比当前距离小,例如x-y,x一直减小对于从右向左走,y不变对应栈顶不弹出。。。)

int maxWidthRamp(vector<int>& nums) {
	int stack[50001] = { 0 };
	r = 1; // 令r=1相当于0位置进栈了
	int n = nums.size();
	// 构建小压大的单调栈 -- 存储的是数组的下标
	for (int i = 1; i < n; i++) {
		if (nums[stack[r - 1]] > nums[i]) {
			stack[r++] = i;
		}
	}
	int ans = 0;
	// 从右往左找答案,比栈顶元素大,计算答案,栈顶弹出
	for (int j = n - 1; j >= 0; j--) {
		while (r > 0 && nums[stack[r - 1]] <= nums[j]) {
			ans = max(ans, j - stack[--r]);
		}
	}
	return ans;
}

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

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

相关文章

性价比高的宠物空气净化器选购指南,双十一有哪几款值得购买?

养猫家庭注意养猫家庭注意&#xff0c;换毛季它又来啦&#xff01;不管你家猫是多么瘦小&#xff0c;这个时候都会变成一年两次限定的蒲公英小猫。这都是因为它在疯狂的掉毛&#xff0c;没来得及清理的毛发就留在身上&#xff0c;不断堆积&#xff0c;家里也到处都是它掉落的猫…

医学影像学基础:理解CT、MRI、X射线和超声等医学影像设备的基本工作原理和成像技术

目录 医学影像学基础 1. X射线成像 2. 计算机断层扫描&#xff08;CT&#xff09; 3. 磁共振成像&#xff08;MRI&#xff09; 4. 超声成像 综合对比 1、成像原理对比 2、安全性对比 3、应用领域对比 4、设备特点对比 总结 医学影像学基础 在医学影像学中&#xff0…

TCP simultaneous open测试

源代码 /*************************************************************************> File Name: common.h> Author: hsz> Brief:> Created Time: 2024年10月23日 星期三 09时47分51秒**********************************************************************…

windows录屏软件工具推荐!!

如今&#xff0c;科技的进步&#xff0c;互联网的普及&#xff0c;使我们的生活越来越便利&#xff0c;录屏工具的出现&#xff0c;大大提高我们的工作效率。如果你经常需要录制屏幕上的内容&#xff0c;比如制作教学视频、游戏实况记录、演示文稿等等&#xff0c;那这几款软件…

“令牌化”革命:数据货币化如何重塑企业竞争格局

在科技日新月异的今天&#xff0c;英伟达CEO黄仁勋在Gartner IT研讨会/XPO大会上的主题演讲无疑为企业创业者们提供了一场思想的盛宴。作为科技行业的领军企业&#xff0c;英伟达不仅在图形处理器&#xff08;GPU&#xff09;领域取得了巨大成功&#xff0c;更在人工智能&#…

前端新人手册:入职第一天的环境配置秘籍

在前端开发的世界里&#xff0c;一个高效、稳定的开发环境是高效工作的基石。它不仅能够提升你的工作效率&#xff0c;还能帮助你更快地适应团队的工作节奏。本文将详细介绍前端开发需要具备的环境及工具。 开发环境 Node.js 通常我们的前端项目都是依赖Node.js环境的&#…

JavaScript入门中-流程控制语句

本文转载自&#xff1a;https://fangcaicoding.cn/article/52 大家好&#xff01;我是方才&#xff0c;目前是8人后端研发团队的负责人&#xff0c;拥有6年后端经验&3年团队管理经验&#xff0c;截止目前面试过近200位候选人&#xff0c;主导过单表上10亿、累计上100亿数据…

C++ 日志管理 spdlog 使用笔记

文章目录 Part.I IntroductionChap.I 预备知识Chap.II 常用语句 Part.II 使用Chap.I 简单使用Chap.II 自定义日志格式 Part.III 问题&解决方案Chap.I 如果文件存在则删除 Reference Part.I Introduction spdlog 是一个开源的 C 日志管理工具&#xff0c;Git 上面的地址为 …

Ovis原理解读: 多模态大语言模型的结构嵌入对齐

论文&#xff1a;https://arxiv.org/pdf/2405.20797 github:https://github.com/AIDC-AI/Ovis 在多模态大语言模型 (MLLM) 中&#xff0c;不同的嵌入策略有显著的区别。以下是使用基于连接器的方法与 Ovis 方法的比较&#xff1a; 基于连接器的方法-优缺点(connector-based …

WPF+MVVM案例实战(十)- 水波纹按钮实现与控件封装

文章目录 1、运行效果1、封装用户控件1、创建文件2、依赖属性实现 2、使用封装的按钮控件1.主界面引用2.按钮属性设置 3 总结 1、运行效果 1、封装用户控件 1、创建文件 打开 Wpf_Examples 项目&#xff0c;在 UserControlLib 用户控件库中创建按钮文件 WaterRipplesButton.x…

产品结构设计(五):结构设计原则

1. 产品结构设计总原则 1.1 合理选用材料 1、根据产品应用场所来选择 如果为日常消费类电子产品&#xff0c;产品材料就应选用强度好、表面容易处理、不容易氧化生锈、不容易磨伤、易成型的材料&#xff0c;如塑胶材料选用 PC、ABS、PCABS 等&#xff0c;金属材料选用不锈钢、…

一些待机电流波形特征

一、待机电流波形 最干净的待机电流波形应该只有paging&#xff0c;不过需要注意2点&#xff1a; 每个paging的间隔&#xff0c;不同网络可能不一样&#xff0c;有可能是320ms, 640ms 待机网络 paging 间隔 1分钟的耗电量 单个耗电量 单个待机电流 单个波形时长 4G 64…

你了解kafka消息队列么?

消息队列概述 一. 消息队列组件二. 消息队列通信模式2.1 点对点模式2.2 发布/订阅模式 三. 消息队列的优缺点3.1 消息队列的优点3.2 消息队列的缺点 四. 总结 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&…

uniapp使用easyinput文本框显示输入的字数和限制的字数

uniapp使用easyinput文本框显示输入的字数和限制的字数 先上效果图&#xff1a; 整体代码如下&#xff1a; <template><view class"nameInfoContent"><uni-easyinput class"uni-mt-5" suffixIcon"checkmarkempty" v-model&quo…

Linux云计算 |【第五阶段】CLOUD-DAY4

主要内容&#xff1a; Linux容器基础、安装Docker、镜像管理、容器管理、容器部署应用 一、容器介绍 容器&#xff08;Container&#xff09; 是一种轻量级的虚拟化技术&#xff0c;用于在操作系统级别隔离应用程序及其依赖项。容器允许开发者在同一台主机上运行多个独立的应…

MaskGCT,AI语音克隆大模型本地部署(Windows11),基于Python3.11,TTS,文字转语音

前几天&#xff0c;又一款非自回归的文字转语音的AI模型&#xff1a;MaskGCT&#xff0c;开放了源码&#xff0c;和同样非自回归的F5-TTS模型一样&#xff0c;MaskGCT模型也是基于10万小时数据集Emilia训练而来的&#xff0c;精通中英日韩法德6种语言的跨语种合成。数据集Emili…

《数字图像处理基础》学习03-图像的采样

在之前的学习中我已经知道了图像的分类&#xff1a;物理图像和虚拟图像。《数字图像处理基础》学习01-数字图像处理的相关基础知识_图像处理 数字-CSDN博客 目录 一&#xff0c;连续图像和离散图像的概念 二&#xff0c;图像的采样 1&#xff0c; 不同采样频率采样同一张图…

SSA-CNN-LSTM-MATT多头注意力机制多特征分类预测

SSA-CNN-LSTM-MATT多头注意力机制多特征分类预测 目录 SSA-CNN-LSTM-MATT多头注意力机制多特征分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现SSA-CNN-LSTM-MATT麻雀算法优化卷积神经网络-长短期记忆神经网络融合多头注意力机制多特征分类预测&…

ComfyUI - 视觉基础任务 检测(Detection) 和 分割(Segmentation) 的 Impact-Pack 流程 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/141140498 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 在 Com…

【音视频 | ADPCM】音频编码ADPCM详细介绍及例子

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…