当前位置: 首页 > news >正文

【数据结构】单调栈(模板 + 练习)

文章目录

  • 一、单调栈
    • 1. 什么是单调栈
    • 2. 单调栈解决的问题
    • 3. 【模板】单调栈 ⭐⭐
  • 二、OJ 练习
    • 1. 发射站 ⭐⭐
      • (1) 解题思路
      • (2) 代码实现
    • 2. 最大的矩阵纸片 ⭐⭐⭐
      • (1) 解题思路
      • (2) 代码实现

一、单调栈

1. 什么是单调栈

单调栈,顾名思义,就是具有单调性的栈。它依旧是一个栈结构,只不过里面存储的数据是递增或者递减的。这种结构是很容易实现的,如下面的代码。

注:我们这里说的递增和递减是严格递增严格递减

#include <iostream>
#include <stack>using namespace std;const int N = 3e6 + 10;
int a[N], n;// 遍历 a 数组, 往栈中放元素, 保证栈中的元素是单增或单减的
// 维护一个单调递增的栈 
void test1()
{stack<int> st;// 遍历 a 数组for(int i = 1; i <= n; i++){// 现在要放 a[i], 如果栈不空并且栈顶元素大于等于 a[i], 就把栈顶元素出栈// 直到栈顶元素小于 a[i]while(st.size() && st.top() >= a[i]) st.pop();// 之后再把 a[i] 压栈st.push(a[i]);}// 通过这样的 while 循环, 就可以保证无论什么时候栈中的元素都是递增的
}// 维护一个单调递减的栈 
void test2()
{stack<int> st; for(int i = 1; i <= n; i++){// 现在要放 a[i], 如果栈不空并且栈顶元素小于等于 a[i], 就把栈顶元素出栈// 直到栈顶元素小于 a[i]while(st.size() && st.top() <= a[i]) st.pop();// 之后再把 a[i] 压栈st.push(a[i]);}// 通过这样的 while 循环, 就可以保证无论什么时候栈中的元素都是递减的
}

2. 单调栈解决的问题

这样去维护一个单调栈的意义是什么?

单调栈能帮助我们解决以下四个问题,在一个数组中:

  • 寻找当前元素左侧,离它最近,并且比它大的元素在哪;

  • 寻找当前元素左侧,离它最近,并且比它小的元素在哪;

  • 寻找当前元素右侧,离它最近,并且比它大的元素在哪;

  • 寻找当前元素右侧,离它最近,并且比它小的元素在哪。

虽然是四个问题,但是原理是一致的。因此,只要解决一个,举⼀反三就可以解决剩下的几个。

我们首先来看单调栈是如何解决第一个问题的:寻找当前元素左侧,离它最近,并且比它大的元素在哪。

从左往右遍历元素,构造一个单调递减的栈。当 while 循环弹栈完成后插入当前位置的元素的时:

  • 如果栈为空,则左侧不存在比当前元素大的元素;

  • 如果栈非空,插入当前位置元素时的栈顶元素就是所找的元素。

注意,因为我们要找的是最终结果的位置。因此,栈里面存的是每个元素的下标

第二个问题:寻找当前元素左侧,离它最近,并且比它小的元素在哪。

从左往右遍历元素,构造一个单调递增的栈。当 while 循环弹栈完成后插入当前位置的元素的时:

  • 如果栈为空,则左侧不存在比当前元素小的元素;

  • 如果栈非空,插入当前位置元素时的栈顶元素就是所找的元素。

注意,因为我们要找的是最终结果的位置。因此,栈里面存的是每个元素的下标

第三个问题:寻找当前元素右侧,离它最近,并且比它大的元素在哪。

从右往左遍历元素,构造一个单调递减的栈。当 while 循环弹栈完成后插入当前位置的元素的时:

  • 如果栈为空,则右侧不存在比当前元素大的元素;

  • 如果栈非空,插入当前位置元素时的栈顶元素就是所找的元素。

注意,因为我们要找的是最终结果的位置。因此,栈里面存的是每个元素的下标

第四个问题:寻找当前元素右侧,离它最近,并且比它小的元素在哪。

从右往左遍历元素,构造一个单调递增的栈。当 while 循环弹栈完成后插入当前位置的元素的时:

  • 如果栈为空,则右侧不存在比当前元素小的元素;

  • 如果栈非空,插入当前位置元素时的栈顶元素就是所找的元素。

注意,因为我们要找的是最终结果的位置。因此,栈里面存的是每个元素的下标

通过以上方法,我们可以总结出以下结论:

找左侧,正遍历;找右侧,逆遍历;

比它大,单调减;比它小,单调增。


3. 【模板】单调栈 ⭐⭐

题目链接

P5788 【模板】单调栈 - 洛谷

【题目背景】

模板题,无背景。

2019.12.12 更新数据,放宽时限,现在不再卡常了。

【题目描述】

给出项数为 n n n 的整数数列 a 1 … n a_{1 \dots n} a1n

定义函数 f ( i ) f(i) f(i) 代表数列中第 i i i 个元素之后第一个大于 a i a_i ai 的元素的下标,即 f ( i ) = min ⁡ i < j ≤ n , a j > a i { j } f(i)=\min_{i<j\leq n, a_j > a_i} \{j\} f(i)=mini<jn,aj>ai{j}。若不存在,则 f ( i ) = 0 f(i)=0 f(i)=0

试求出 f ( 1 … n ) f(1\dots n) f(1n)

【输入格式】

第一行一个正整数 n n n

第二行 n n n 个正整数 a 1 … n a_{1\dots n} a1n

【输出格式】

一行 n n n 个整数表示 f ( 1 ) , f ( 2 ) , … , f ( n ) f(1), f(2), \dots, f(n) f(1),f(2),,f(n) 的值。

【示例一】

输入

5
1 4 2 3 5

输出

2 5 4 5 0

【说明/提示】

数据规模与约定

对于 30 % 30\% 30% 的数据, n ≤ 100 n\leq 100 n100

对于 60 % 60\% 60% 的数据, n ≤ 5 × 1 0 3 n\leq 5 \times 10^3 n5×103

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 3 × 1 0 6 1 \le n\leq 3\times 10^6 1n3×106 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1ai109


很明显,这道题要我们找每一个元素右侧,离它最近,比它大的元素的位置。因此我们只需要逆序遍历数组,并维护一个单调递减的单调栈即可。

#include<iostream>
#include<stack>using namespace std;const int N = 3e6 + 10;
int a[N];
int f[N];  // 存储每一个位置右侧离它最近并且比它大的元素的下标int main()
{int n;cin >> n;for(int i = 1; i <= n; i++) cin >> a[i];stack<int> st;// 逆序遍历数组for(int i = n; i >= 1; i--){// 维护单调递减的单调栈// 如果栈不空并且栈顶元素小于等于 a[i] while(st.size() && a[st.top()] <= a[i]) st.pop();// 此时如果栈不空,则栈顶元素就是要找的位置if(st.size()) f[i] = st.top();// 否则就没有比它大的元素,就存 0else f[i] = 0;st.push(i);}for(int i = 1; i <= n; i++) cout << f[i] << ' ';return 0;
}

二、OJ 练习

1. 发射站 ⭐⭐

【题目链接】

P1901 发射站 - 洛谷

【题目描述】

某地有 N N N 个能量发射站排成一行,每个发射站 i i i 都有不相同的高度 H i H_i Hi,并能向两边(两端的发射站只能向一边)同时发射能量值为 V i V_i Vi 的能量,发出的能量只被两边最近的且比它高的发射站接收。显然,每个发射站发来的能量有可能被 0 0 0 1 1 1 2 2 2 个其他发射站所接受。

请计算出接收最多能量的发射站接收的能量是多少。

【输入格式】

1 1 1 行一个整数 N N N

2 2 2 N + 1 N+1 N+1 行,第 i + 1 i+1 i+1 行有两个整数 H i H_i Hi V i V_i Vi,表示第 i i i 个发射站的高度和发射的能量值。

【输出格式】

输出仅一行,表示接收最多能量的发射站接收到的能量值。答案不超过 32 位带符号整数的表示范围。

【示例一】

输入

3
4 2 
3 5 
6 10

输出

7

【说明/提示】

对于 40 % 40\% 40% 的数据, 1 ≤ N ≤ 5000 , 1 ≤ H i ≤ 1 0 5 , 1 ≤ V i ≤ 1 0 4 1\le N\le 5000,1\le H_i\le 10^5,1\le V_i\le 10^4 1N5000,1Hi105,1Vi104

对于 70 % 70\% 70% 的数据, 1 ≤ N ≤ 1 0 5 , 1 ≤ H i ≤ 2 × 1 0 9 , 1 ≤ V i ≤ 1 0 4 1\le N\le 10^5,1\le H_i\le 2\times 10^9,1\le V_i\le 10^4 1N105,1Hi2×109,1Vi104

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 6 , 1 ≤ H i ≤ 2 × 1 0 9 , 1 ≤ V i ≤ 1 0 4 1\le N\le 10^6,1\le H_i\le 2\times 10^9,1\le V_i\le 10^4 1N106,1Hi2×109,1Vi104


(1) 解题思路

很明显的一个用单调栈解决的问题。我们可以用一个数组 val[] 来记录每个发射站接收到的能量总和,对于每一个发射站,我们利用单调栈向左和向右去寻找第一个比它高的发射站,让找到的这两个发射站对应的 val += v[i] 。最后遍历整个 val 数组,找出最大值即可。


(2) 代码实现

#include<iostream>
#include<stack>using namespace std;const int N = 1e6 + 10;
int h[N], v[N];  // 分别记录高度和发射的能量大小
int val[N];  // 记录每个发射站接收到的能量总和int main()
{int n; cin >> n;for(int i = 1; i <= n; i++) cin >> h[i] >> v[i];// 向右边发射 -> 向右找大,逆序单减stack<int> st;for(int i = n; i >= 1; i--){while(st.size() && h[st.top()] <= h[i]) st.pop();if(st.size()) val[st.top()] += v[i];st.push(i);}while(st.size()) st.pop();  // 清空栈// 向左边发射 -> 向左找大,正序单减for(int i = 1; i <= n; i++){while(st.size() && h[st.top()] <= h[i]) st.pop();if(st.size()) val[st.top()] += v[i];st.push(i);}int ans = 0;for(int i = 1; i <= n; i++) ans = max(ans, val[i]);cout << ans;return 0;
}

2. 最大的矩阵纸片 ⭐⭐⭐

【题目链接】

[B4273 蓝桥杯青少年组省赛 2023] 最大的矩形纸片 - 洛谷

【题目描述】

一张半边参差不齐的网格纸(网格边长均为 1 1 1),有一边是完整没有破损的。现要从中剪出一片面积最大的矩形纸片。

给定网格纸中完整边的长度 N N N 1 ≤ N ≤ 1 000 000 1 \leq N \leq 1\,000\,000 1N1000000),以及网格中每一列残存部分的高度( 1 ≤ 1 \leq 1 高度 ≤ 10 000 \leq 10\,000 10000),输出能够剪出的最大矩形纸片面积。

【输入格式】

第一行输入一个正整数 N N N 1 ≤ N ≤ 1 000 000 1 \leq N \leq 1\,000\,000 1N1000000),表示纸片完整边的长度。

第二行输入 N N N 个正整数( 1 ≤ 1 \leq 1 正整数 ≤ 10 000 \leq 10\,000 10000),表示每列格子残存部分的高度,两个正整数之间用一个空格隔开。

【输出格式】

输出一个正整数,表示能够剪出的最大矩形纸片面积。

【示例一】

输入

6
3 2 1 4 5 2

输出

8

(1) 解题思路

题意可以理解为给了一排宽度为 1,高度给定的矩形纸片,求能剪出的最大矩形的面积,如图:

image-20251018103414666

对于一个高度为 h 的矩形,想要从这个位置向左或者向右去拼成一个更大的矩形,很明显,需要它左侧和右侧纸的高度比它高。换言之,我们就是要找它左侧和右侧第一个比它矮的位置,假设左侧第一个比它矮的位置为 l(如果没有,记为 0),右侧第一个比它矮的位置为 r(如果没有,记为 n + 1),那么以当前矩形向两边扩展所能剪出的最大的矩形面积就是 h * (r - l - 1)

想要求出最大的矩阵面积,只需要对于每一个高度的矩形,都向左和向右寻找第一个比它矮的位置,计算出所能得到的矩形的面积,然后取最大值即可。


(2) 代码实现

#include<iostream>
#include<stack>
#include<cmath>using namespace std;typedef long long LL;const int N = 1e6 + 10;
// 分别记录高度,左侧第一个比它小,右侧第一个比它小
LL h[N], l[N], r[N];int main()
{int n; cin >> n;for(int i = 1; i <= n; i++) cin >> h[i];// 向右边找第一个比它小的元素stack<int> st;for(int i = n; i >= 1; i--){while(st.size() && h[st.top()] >= h[i]) st.pop();if(st.size()) r[i] = st.top();else r[i] = n + 1;st.push(i);}while(st.size()) st.pop();// 向左边找第一个比它小的元素for(int i = 1; i <= n; i++){while(st.size() && h[st.top()] >= h[i]) st.pop();if(st.size()) l[i] = st.top();else l[i] = 0;st.push(i);}LL ret = 0;for(int i = 1; i <= n; i++){LL t = h[i] * (r[i] - l[i] - 1);ret = max(ret, t);}cout << ret << endl;return 0;
}
http://www.dtcms.com/a/588911.html

相关文章:

  • 整体设计 全面梳理复盘 之26 九宫格框架与一体化开发 编程 之5
  • LeetCode算法学习之有效的字母异位词
  • 【算法】递归算法的深度实践:深度优先搜索(DFS)从原理到LeetCode实战
  • BFS 图论【各种题型+对应LeetCode习题练习】
  • 威联通怎么建设网站人类命运共同体
  • 【ElasticSearch实用篇-05】基于脚本script打分
  • 微前端框架选型
  • Java 17 密封类(Sealed Classes)实战:从类型安全到架构解耦的范式升级
  • 保健品网站模板wordpress简约主题分享
  • 前端低代码平台
  • 八字排盘原理
  • 40.交叉编译
  • RT-Thread Studio开发环境搭建
  • jdbc基础(连接篇)
  • 免费云服务器网站有哪些为什么手机进网站乱码
  • 从入门到精通 LlamaIndex RAG 应用开发
  • 算法基础篇:(五)基础算法之差分——以“空间”换“时间”
  • 潍坊中企动力做的网站怎么样wordpress显示摘要
  • leetcode1771.由子序列构造的最长回文串长度
  • 【JUnit实战3_31】第十九章:基于 JUnit 5 + Hibernate + Spring 的数据库单元测试
  • 双11释放新增量,淘宝闪购激活近场潜力
  • MySQL快速入门——内置函数
  • 中小网站建设都有哪些网易企业邮箱申请
  • 预测电流控制在光伏逆变器中的低延迟实现:华为FPGA加速方案与并网稳定性验证
  • C语言--文件读写函数的使用
  • 网站的网站维护的原因可以做公众号的网站
  • 使用waitpid回收多个子进程
  • leetcode1547.切棍子的最小成本
  • ThinkPHP8学习篇(十一):模型关联(一)
  • 深入理解Ribbon的架构原理