下一个更大元素
下一个更大元素问题的单调栈解法:Luogu P5788 模板题。
一句话算法
新来的大元素负责结算它左边所有比它小、且还没找到答案的位置。
问题模型
对每个位置 0。
完整讲解见单调栈。
核心直觉
栈中保存还没找到答案的位置。若当前值比栈顶大,当前下标就是栈顶位置的答案;弹出后继续尝试结算新的栈顶。
算法步骤
- 初始化答案数组为
0,表示默认不存在右侧第一个更大元素。 - 用栈保存“还没找到答案”的下标。
- 从左到右枚举当前位置
i。 - 当栈非空且
a[栈顶] < a[i]时:- 当前
i就是栈顶位置的右侧第一个更大元素; - 记录
ans[栈顶]=i; - 弹出栈顶,继续尝试结算新的栈顶。
- 当前
- 将
i入栈,等待后续更大元素结算它。 - 扫描结束后,栈中剩余位置没有答案,保持
0。
算法证明
核心不变量: 扫描到位置 i 前,栈中保存的下标都还没有在已扫描部分找到右侧第一个更大元素,且栈中对应值单调不增。
- 弹栈正确。 若当前
a[i]大于栈顶a[j],并且j之前没有被弹出,说明到 中没有元素大于 a[j]。因此i是j右侧第一个更大元素。 - 未弹出正确。 若栈顶
a[j] >= a[i],当前元素不能成为j的答案,j应继续等待。 - 单调性保持。 弹出所有小于
a[i]的栈顶后,再把i入栈,栈内值仍然单调不增。 - 不漏答案。 每个位置在遇到第一个能弹出它的更大元素时立即结算;若一直没有被弹出,答案就是不存在。
因此算法能正确求出每个位置右侧第一个更大元素。
复杂度分析
每个下标最多入栈一次、出栈一次。
- 时间复杂度:
。 - 空间复杂度:
。
代码实现
cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n + 1), ans(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
// 栈中保存还没有找到右侧第一个更大元素的位置。
vector<int> st;
for (int i = 1; i <= n; i++) {
while (!st.empty() && a[st.back()] < a[i]) {
ans[st.back()] = i;
st.pop_back();
}
st.push_back(i);
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << (i == n ? '\n' : ' ');
}
return 0;
}
测试用例
输入:
5
1 4 2 3 5
输出:
2 5 4 5 0
经典例题
1. luogu-P5788
标准单调栈模板题。注意输出的是位置,不是值。
2. LeetCode 739
求右侧第一个更高温度的距离,结算时输出 i - top。
应用分类详解
下一个更大元素的本质是:新元素负责结算左侧所有被它第一次超过的候选。
一、右侧第一个更大/更小
- 典型模式: 对每个位置,求它右边第一个更大、更小、更高或更低的位置。
- 识别信号: 出现“右侧第一个”“下一个更大”“等待多少天才更高”。
- 核心建模: 栈中保存未结算下标,新元素满足条件时弹栈并记录答案。
| 应用场景 | 经典题目 | 核心思路 |
|---|---|---|
| 单调栈模板 | luogu-P5788 | 求右侧第一个更大元素位置 |
| 每日温度 | LeetCode 739 | 答案从位置改成距离 i-top |
二、左侧最近更大/更小
- 典型模式: 查询每个位置左边最近一个满足大小关系的位置。
- 识别信号: 出现“左侧第一个”“前面最近的更高建筑”。
- 核心建模: 从左到右扫描,先维护栈,再读取栈顶作为左侧最近候选。
| 应用场景 | 经典题目 | 核心思路 |
|---|---|---|
| 最近更小元素 | 常见边界题 | 弹掉不满足条件的元素后读栈顶 |
| 区间边界预处理 | 直方图最大矩形 | 左右最近更小决定可扩展范围 |