news 2026/2/18 13:05:27

算法基础-(数据结构)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法基础-(数据结构)

1.单调栈

1.什么是单调栈?

单调栈,顾名思义,就是具有单调性的栈。它依旧是⼀个栈结构,只不过⾥⾯存储的数据是递增或者 递减的。这种结构是很容易实现的(如下⾯的代码),但重点是维护⼀个单调栈的意义是什么?
#include <iostream> #include <stack> using namespace std; const int N = 3e6 + 10; int a[N], n; // 单调递增栈 void test1() { stack<int> st; for (int i = 1; i <= n; i++) { while (st.size() && st.top() >= a[i]) st.pop(); st.push(a[i]); } // 输出栈内元素(递增) cout << "单调递增栈结果:"; while (!st.empty()) { cout << st.top() << " "; st.pop(); } cout << endl; } // 单调递减栈 void test2() { stack<int> st; for (int i = 1; i <= n; i++) { while (st.size() && st.top() <= a[i]) st.pop(); st.push(a[i]); } // 输出栈内元素(递减) cout << "单调递减栈结果:"; while (!st.empty()) { cout << st.top() << " "; st.pop(); } cout << endl; } int main() { // 测试用例:n=5,数组a=[3,1,4,2,5] n = 5; a[1] = 3; a[2] = 1; a[3] = 4; a[4] = 2; a[5] = 5; test1(); // 递增栈结果:1 2 5 test2(); // 递减栈结果:5 4 3 return 0; }

2.单调栈解决的问题

单调栈能帮助我们解决以下四个问题:
• 寻找当前元素左侧,离它最近,并且⽐它⼤的元素在哪;
• 寻找当前元素左侧,离它最近,并且⽐它⼩的元素在哪;
• 寻找当前元素右侧,离它最近,并且⽐它⼤的元素在哪;
• 寻找当前元素右侧,离它最近,并且⽐它⼩的元素在哪。
虽然是四个问题,但是原理是⼀致的。因此,只要解决⼀个,举⼀反三就可以解决剩下的⼏个。

3.寻找当前元素左侧,离它最近,并且⽐它⼤的元素在哪

从左往右遍历元素,构造⼀个单调递减的栈。插⼊当前位置的元素的时:
• 如果栈为空,则左侧不存在⽐当前元素⼤的元素;
• 如果栈⾮空,插⼊当前位置元素时的栈顶元素就是所找的元素。
注意,因为我们要找的是最终结果的位置。因此,栈⾥⾯存的是每个元素的下标。
#include <iostream> #include <stack> using namespace std; const int N = 3e6 + 10; int a[N], n; int ret[N]; void test() { stack<int> st; // 单调递增栈:存储下标 for (int i = 1; i <= n; i++) ret[i] = 0; // 逆序遍历:找右侧第一个更小的元素 for (int i = n; i >= 1; i--) { // 弹出栈中≥当前值的下标 while (st.size() && a[st.top()] >= a[i]) { st.pop(); } // 栈非空:右侧第一个更小元素的值 if (st.size()) { ret[i] = a[st.top()]; } st.push(i); } for (int i = 1; i <= n; i++) { cout << ret[i] << " "; } cout << endl; } int main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } test(); return 0; }

4.寻找当前元素左侧,离它最近,并且⽐它⼩的元素在哪

从左往右遍历元素,构造⼀个单调递增的栈。插⼊当前位置的元素的时:
• 如果栈为空,则左侧不存在⽐当前元素⼩的元素;
• 如果栈⾮空,插⼊当前位置元素时的栈顶元素就是所找的元素。
注意,因为我们要找的是最终结果的位置。因此,栈⾥⾯存的是每个元素的下标。
#include <iostream> // 输入输出头文件:用来输入数字、输出结果 #include <stack> // 栈的头文件:用来使用“栈”这个工具 using namespace std; // 定义数组最大长度,3e6+10表示足够装下大量数字(不用改) const int N = 3e6 + 10; int a[N], n; // a[N]:装输入的数字;n:装数字的总个数 int ret[N]; // ret[N]:装最终结果(每个数左边最近更小的数的下标) void test() { // 定义一个栈,栈里存的是数字的“下标”(抽屉编号),维护单调递增的栈 stack<int> st; // 从第1个数字到第n个数字,逐个处理 for(int i = 1; i <= n; i++) { // 核心逻辑:把栈里≥当前数字的下标都弹出(保证栈是递增的) // st.size():判断栈里有没有东西;a[st.top()]:栈顶下标对应的数字 while(st.size() && a[st.top()] >= a[i]) { st.pop(); // 弹出栈顶(扔掉不符合的下标) } // 如果栈里还有东西,栈顶就是“左边最近更小的数的下标” if(st.size()) { ret[i] = st.top(); } else { ret[i] = 0; // 栈空=没找到,填0 } // 把当前数字的下标放进栈里,维护栈的递增性 st.push(i); } // 输出结果:从第1个到第n个,逐个打印ret里的数 for(int i = 1; i <= n; i++) { cout << ret[i] << " "; } cout << endl; } int main() { // 第一步:输入数字的总个数(测试用例里是9) cin >> n; // 第二步:输入n个数字,依次放进a[1]到a[n](抽屉1到抽屉n) for(int i = 1; i <= n; i++) { cin >> a[i]; } // 第三步:执行找“左边最近更小数下标”的逻辑 test(); return 0; }

5.寻找当前元素右侧,离它最近,并且⽐它⼤的元素在哪

从右往左遍历元素,构造⼀个单调递减的栈。插⼊当前位置的元素的时:
• 如果栈为空,则左侧不存在⽐当前元素⼤的元素;
• 如果栈⾮空,插⼊当前位置元素时的栈顶元素就是所找的元素。
注意,因为我们要找的是最终结果的位置。因此,栈⾥⾯存的是每个元素的下标。
#include <iostream> // 输入输出工具:能输入数字、输出结果 #include <stack> // 栈工具:用来找“右侧最近更大的数” using namespace std; const int N = 3e6 + 10; // 准备足够多的“抽屉”(不用改,够装数字就行) int a[N], n; // a抽屉:装输入的9个数字;n:记数字的总个数(比如9) int ret[N]; // ret抽屉:装最终结果(每个数的答案) void test() { stack<int> st; // 拿出空的“栈盒子”(维护单调递减,只存下标) // 核心:从最后一个数往第一个数遍历(右→左) for(int i = n; i >= 1; i--) { // 第一步:把栈里≤当前数的下标都扔掉(保证栈是递减的) // st.size():盒子里有东西吗?a[st.top()]:栈顶下标对应的数字 while(st.size() && a[st.top()] <= a[i]) { st.pop(); // 扔掉不符合的下标 } // 第二步:填结果 if(st.size()) { ret[i] = st.top(); // 栈非空→栈顶是“右侧最近更大数的下标” } else { ret[i] = 0; // 栈空→没找到,填0 } // 第三步:把当前数的下标放进栈里 st.push(i); } // 输出结果:从第1个到第9个,逐个打印ret抽屉里的数 for(int i = 1; i <= n; i++) { cout << ret[i] << " "; } cout << endl; } int main() { cin >> n; // 输入9(告诉程序有9个数) // 把1、4、10、6、3、3、15、21、8依次放进a[1]~a[9] for(int i = 1; i <= n; i++) { cin >> a[i]; } test(); // 执行找“右侧最近更大数下标”的逻辑 return 0; }

6.寻找当前元素右侧,离它最近,并且⽐它⼩的元素在哪

从右往左遍历元素,构造⼀个单调递增的栈。插⼊当前位置的元素的时:
• 如果栈为空,则左侧不存在⽐当前元素⼩的元素;
• 如果栈⾮空,插⼊当前位置元素时的栈顶元素就是所找的元素。
注意,因为我们要找的是最终结果的位置。因此,栈⾥⾯存的是每个元素的下标。
#include <iostream> // 输入输出工具:输入数字、输出结果 #include <stack> // 栈工具:找右侧最近更小的数 using namespace std; const int N = 3e6 + 10; // 足够多的“抽屉”,装数字和结果 int a[N], n; // a:装输入的数字;n:数字总个数 int ret[N]; // ret:装最终结果(每个数的答案) void test() { stack<int> st; // 空栈,维护单调递增(只存下标) // 从最后一个数往第一个数遍历(右→左) for(int i = n; i >= 1; i--) { // 核心:弹出栈里≥当前数的下标(保证栈递增) // st.size():栈里有东西吗?a[st.top()]:栈顶下标对应的数字 while(st.size() && a[st.top()] >= a[i]) { st.pop(); // 扔掉不符合的下标 } // 填结果:栈非空=找到答案,栈空=填0 if(st.size()) { ret[i] = st.top(); } else { ret[i] = 0; // 补充初始化,避免随机值 } // 把当前数的下标放进栈,维护递增性 st.push(i); } // 输出结果:从第一个数到最后一个数打印 for(int i = 1; i <= n; i++) { cout << ret[i] << " "; } cout << endl; } int main() { cin >> n; // 输入数字个数(比如9) // 把数字依次放进a[1]~a[n] for(int i = 1; i <= n; i++) { cin >> a[i]; } test(); // 执行找数逻辑 return 0; }

💡总结:

• 找左侧,正遍历;找右侧,逆遍历;
• ⽐它⼤,单调减;⽐它⼩,单调增。

1.1【模板】单调栈

题⽬来源: 洛⾕
题⽬链接:P5788 【模板】单调栈
难度系数: ★★

题目背景

模板题,无背景。

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

题目描述

给出项数为 n 的整数数列 a1…n​。

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

试求出 f(1…n)。

输入格式

第一行一个正整数 n。

第二行 n 个正整数 a1…n​。

输出格式

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

输入输出样例

输入 #1复制

5 1 4 2 3 5

输出 #1复制

2 5 4 5 0

说明/提示

【数据规模与约定】

对于 30% 的数据,n≤100;

对于 60% 的数据,n≤5×103 ;

对于 100% 的数据,1≤n≤3×106,1≤ai​≤109。


【解法】

右侧离它最近并且⽐它⼤的元素:
• 逆序遍历数组;
• 构造⼀个单调递减的栈;
• 进栈时,栈顶元素就是最终结果。

【参考代码】

#include <iostream> // 输入输出工具:输入数字、输出结果 #include <stack> // 栈工具:找右侧第一个更大的数 using namespace std; const int N = 3e6 + 10; // 足够多的“抽屉”,装数字和结果(适配3e6的大数据) int n; // 数列的长度(比如示例里的5) int a[N]; // a抽屉:装输入的数列(a[1]=1, a[2]=4...) int ret[N]; // ret抽屉:装每个数的答案(右边第一个更大数的下标,没找到填0) int main() { // 第一步:输入数列长度和数列本身 cin >> n; for(int i = 1; i <= n; i++) { cin >> a[i]; // 把数字依次放进a[1]到a[n] } stack<int> st; // 空栈:维护单调递减,只存“下标”(抽屉号) // 第二步:逆序遍历(从最后一个数到第一个数) for(int i = n; i >= 1; i--) { // 核心:弹出栈里≤当前数的下标(保证栈是递减的) // st.size():栈里有东西吗?a[st.top()]:栈顶下标对应的数字 while(st.size() && a[st.top()] <= a[i]) { st.pop(); // 扔掉不符合的下标 } // 第三步:填答案 if(st.size()) { ret[i] = st.top(); // 栈非空→栈顶是“右边第一个更大数的下标” } else { ret[i] = 0; // 栈空→没找到,填0 } // 第四步:把当前数的下标放进栈,维护递减性 st.push(i); } // 第五步:输出结果(如果要输出“值”,把ret[i]改成a[ret[i]]即可) for(int i = 1; i <= n; i++) { // 示例输出是下标,所以输出ret[i];若要输出值,改成cout << a[ret[i]] << " "; cout << ret[i] << " "; } cout << endl; return 0; }

1.2发射站

题⽬来源: 洛⾕
题⽬链接:P1901 发射站
难度系数: ★★

题目描述

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

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

输入格式

第 1 行一个整数 N。

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

输出格式

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

输入输出样例

输入 #1复制

3 4 2 3 5 6 10

输出 #1复制

7

说明/提示

对于 40% 的数据,1≤N≤5000,1≤Hi​≤105,1≤Vi​≤104。

对于 70% 的数据,1≤N≤105,1≤Hi​≤2×109,1≤Vi​≤104。

对于 100% 的数据,1≤N≤106,1≤Hi​≤2×109,1≤Vi​≤104。

【解法】

有了单调栈之后,这道题就变成模拟题了......

【参考代码】

#include <iostream> // 输入输出工具:输入发射站信息、输出结果 #include <stack> // 栈工具:找“最近更高的站” using namespace std; typedef long long LL; // 防止能量值太大溢出(比如多个能量相加超过int范围) const int N = 1e6 + 10; // 足够多的“抽屉”,装1e6个发射站的信息 int n; // 发射站的总数(比如示例里的3) LL h[N], v[N]; // h:装每个站的高度;v:装每个站的能量值 LL sum[N]; // sum:装每个站接收的总能量(初始都是0) int main() { // 第一步:输入发射站数量和每个站的高度、能量 cin >> n; for(int i = 1; i <= n; i++) { cin >> h[i] >> v[i]; // 站1的h=4、v=2;站2的h=3、v=5;站3的h=6、v=10 } // 第二步:找每个站“左边最近更高的站”,并把能量加给这个站 stack<int> st; // 空栈:维护单调递减(只存站的下标) for(int i = 1; i <= n; i++) { // 从左到右遍历每个站 // 核心:弹出栈里≤当前站高度的下标(保证栈是递减的) while(st.size() && h[st.top()] <= h[i]) { st.pop(); } // 栈非空:栈顶就是“左边最近更高的站”,接收当前站的能量 if(st.size()) { sum[st.top()] += v[i]; // 比如站2的左边更高是站1,sum[1] +=5 } // 把当前站的下标放进栈,维护递减性 st.push(i); } // 第三步:找每个站“右边最近更高的站”,并把能量加给这个站 while(st.size()) st.pop(); // 清空之前的栈 for(int i = n; i >= 1; i--) { // 从右到左遍历每个站 // 核心:弹出栈里≤当前站高度的下标(保证栈是递减的) while(st.size() && h[st.top()] <= h[i]) { st.pop(); } // 栈非空:栈顶就是“右边最近更高的站”,接收当前站的能量 if(st.size()) { sum[st.top()] += v[i]; // 比如站1的右边更高是站3,sum[3] +=2 } // 把当前站的下标放进栈,维护递减性 st.push(i); } // 第四步:找接收能量最多的站 LL ret = 0; // 存最大能量值(初始0) for(int i = 1; i <= n; i++) { ret = max(ret, sum[i]); // 逐个比较,保留最大的 } cout << ret << endl; // 输出7(示例) return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/15 20:11:52

【编程和大模型交互】

1.给大模型输入 提问 请帮我优化一下这段代码&#xff0c;并解释优化的原因&#xff0c;请带我精通拜托了。 让我能给别人讲清楚&#xff1b;

作者头像 李华
网站建设 2026/2/8 11:12:28

深入理解 Google Wire:Go 语言的编译时依赖注入框架

什么是依赖注入&#xff1f; 依赖注入&#xff08;Dependency Injection, DI&#xff09;是一种设计模式&#xff0c;用于实现代码的松耦合。在传统的编程模式中&#xff0c;对象通常自己创建或查找它们所依赖的对象&#xff0c;这导致了强耦合。而依赖注入则将对象的创建和依…

作者头像 李华
网站建设 2026/2/9 15:26:58

【完全免费】如何把视频转化为gif动图?这个神器帮你快速完成;一分钟教会你如何视频转换gif动图;它可以设置起始和结束时间,精准转换所需部分。

——软件使用教程—— 如何把视频转化为gif动图&#xff1f;一分钟教会你如何视频转换gif——下载地址&#xff08;防止被拦截&#xff0c;请用浏览器打开&#xff09;—— 夸克地址&#xff1a; https://pan.dxlszyk.com/s/1jcborr41 多盘地址&#xff1a; https://www.dx…

作者头像 李华
网站建设 2026/2/14 15:17:59

BetterDiscord终极个性化定制完全攻略

BetterDiscord终极个性化定制完全攻略 【免费下载链接】BetterDiscordApp Better Discord App enhances Discord desktop app with new features. 项目地址: https://gitcode.com/gh_mirrors/be/BetterDiscordApp 还在用单调的Discord界面吗&#xff1f;想要让聊天体验焕…

作者头像 李华
网站建设 2026/2/17 3:07:44

元素周期表1.0.7更新

说好不更的&#xff0c;但是发现了一点点小问题&#xff0c;所以更新了。更新内容&#xff1a;• 加入了递变相关工具 • 修复了人文功能 • 实装了化合价字段 • 移除了Herobrine、新动画新功能重写网站已同步更新。

作者头像 李华
网站建设 2026/2/3 0:38:08

基于Python+Django的大学生兴趣部落交流系统设计与实现

前言 &#x1f31e;博主介绍&#xff1a;✌CSDN特邀作者、全栈领域优质创作者、10年IT从业经验、码云/掘金/知乎/B站/华为云/阿里云等平台优质作者、专注于Java、小程序/APP、python、大数据等技术领域和毕业项目实战&#xff0c;以及程序定制化开发、文档编写、答疑辅导等。✌…

作者头像 李华