洛谷P5788 【模板】单调栈——单调栈

题目来源

https://www.luogu.com.cn/problem/P5788

题目背景

模板题,无背景。

题目描述

给出项数为 n 的整数数列 a1...n

定义函数 f(i) 代表数列中第 i 个元素之后第一个大于 ai的元素的下标,即 f(i)=min

i<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×10^3;

对于 100% 的数据,1≤n≤3×10^6,1≤ai≤10^9。

算法分析

一、题目背景与核心需求​

首先,我们来明确题目要求,这是解题的第一步。洛谷 P5788 的题目描述如下:​

给定一个长度为 n

的数组

a

,请你找到每个元素右边第一个比它大的元素的值,如果不存在则输出

0

。​

输入格式:第一行输入一个整数

n

1≤n≤3×10

6

),第二行输入

n

个整数,表示数组

a

。​

输出格式:输出一行,包含

n

个整数,依次表示每个元素右边第一个比它大的元素的值。​

从题目中我们能提炼出两个关键信息:​

问题本质:对于数组中的每个元素

a[i]

,寻找其右侧第一个大于

a[i]

的元素,记为

ans[i]

;若没有,则

ans[i]=0

。​

数据规模:

n

最大可达

3×10

6

,这意味着我们必须设计时间复杂度为

O(n)

的算法 ------ 如果使用暴力枚举(对每个元素遍历其右侧所有元素),时间复杂度为

O(n

2

)

,在

n=3e6

时会严重超时,因此需要更高效的解法,而单调栈正是解决这类问题的 "利器"。​

二、单调栈原理:什么是单调栈?​

在讲解解题思路前,我们先明确 "单调栈" 的定义。单调栈是一种特殊的栈结构,其核心特性是:栈内的元素始终保持严格的单调性(递增或递减)。根据单调性的不同,单调栈可分为 "单调递增栈" 和 "单调递减栈":​

单调递增栈:栈内元素从栈底到栈顶依次递增(即每次入栈的元素必须大于栈顶元素,若不满足则弹出栈顶元素,直到满足条件或栈为空)。​

单调递减栈:栈内元素从栈底到栈顶依次递减(同理,每次入栈的元素必须小于栈顶元素,否则弹出栈顶元素)。​

单调栈的优势在于,它能在线性时间内处理数组中 "Next Greater Element""Next Smaller Element" 等问题,其本质是通过 "维护栈的单调性",避免了对元素的重复遍历,将暴力解法的

O(n

2

)

时间复杂度优化到

O(n)

。​

三、解题思路:如何用单调栈解决本题?​

回到洛谷 P5788,我们需要寻找每个元素 "右边第一个更大的元素",此时应选择单调递减栈,原因如下:​

单调递减栈中,栈顶元素是当前未找到 "右边更大元素" 的元素中最小的一个。当遇到一个新元素

a[i]

时,如果

a[i]

大于栈顶元素,说明

a[i]

就是栈顶元素的 "右边第一个更大元素";此时弹出栈顶元素,并记录答案,直到栈顶元素大于

a[i]

或栈为空,再将

a[i]

入栈。​

这样一来,每个元素只会入栈一次、出栈一次,整个过程的时间复杂度为

O(n)

,完全能满足

n=3e6

的数据规模要求。​

具体解题步骤可拆解为以下四步:​

初始化:创建一个空栈(用于存储未找到 "右边更大元素" 的元素下标,而非元素值,这样便于后续记录答案),创建一个长度为

n

的答案数组

ans

,初始值均为

0

(对应 "没有更大元素" 的情况)。​

遍历数组:从数组的第一个元素到最后一个元素,依次处理每个元素

a[i]

i

为当前元素下标)。​

维护栈的单调性:​

当栈不为空,且当前元素

a[i]

大于栈顶下标对应的元素

a[stack.top()]

时:​

弹出栈顶下标

top

,此时

a[i]

就是

a[top]

的 "右边第一个更大元素",因此令

ans[top]=a[i]

。​

重复此过程,直到栈为空或

a[i]≤a[stack.top()]

。​

将当前元素的下标

i

入栈。​

输出答案:遍历结束后,答案数组

ans

中存储的就是每个元素对应的结果,直接输出即可。​

举个例子:直观理解过程​

为了让大家更清晰地掌握步骤,我们以一个小规模数组为例:

a=[3,1,2,4]

n=4

。​

初始化:栈为空,

ans=[0,0,0,0]

。​

处理

i=0

a[0]=3

):栈为空,直接入栈,栈:

0

。​

处理

i=1

a[1]=1

):

a[1]≤a[0]

(1≤3),栈保持递减,入栈,栈:

0,1

。​

处理

i=2

a[2]=2

):​

栈顶下标为 1,

a[2]=2>a[1]=1

,弹出 1,令

ans[1]=2

;​

此时栈顶下标为 0,

a[2]=2≤a[0]=3

,停止弹出,将 2 入栈,栈:

0,2

。​

处理

i=3

a[3]=4

):​

栈顶下标为 2,

a[3]=4>a[2]=2

,弹出 2,令

ans[2]=4

;​

栈顶下标为 0,

a[3]=4>a[0]=3

,弹出 0,令

ans[0]=4

;​

栈为空,将 3 入栈,栈:

3

。​

遍历结束:栈中剩余下标 3(

a[3]=4

),其右侧无元素,

ans[3]

保持 0。​

最终答案:

ans=[4,2,4,0]

,与预期结果一致。​

四、代码实现:从基础版到优化版​

理解了思路后,我们来编写代码。需要注意的是,由于

n

最大为

3e6

,在 C++ 中需注意输入输出效率(使用 scanf/printf 而非 cin/cout,否则可能超时),同时栈的实现可直接使用 STL 的 stack,但为了进一步提升效率,也可使用数组模拟栈(减少 STL 的开销)。

Code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
 
const int maxn=3e6+5;
int idx[maxn], a[maxn];
int stk[maxn];
int n,top;
 
int main() {
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    for(int i=n;i>=1;i--) {
        while(top && a[stk[top]]<=a[i]) {
            top--;
        }
        if(top)idx[i]=stk[top];
        else idx[i]=0;
        stk[++top]=i;
    }
    for(int i=1; i<=n; i++) cout<<idx[i]<<" ";
 
    return 0;
}
相关推荐
Yue丶越6 分钟前
【C语言】深入理解指针(四)
java·c语言·算法
Abona27 分钟前
自动驾驶、无人机、机器人核心技术双范式
算法·机器人·自动驾驶·无人机
草莓熊Lotso28 分钟前
《算法闯关指南:优选算法--模拟》--39.替换所有问号,40.提莫攻击
开发语言·c++·算法·模拟
yuuki2332331 小时前
【数据结构】栈
c语言·数据结构·后端
草莓熊Lotso1 小时前
C++ STL set 系列完全指南:从底层原理、核心接口到实战场景
开发语言·c++·人工智能·经验分享·网络协议·算法·dubbo
做怪小疯子3 小时前
LeetCode 热题 100——子串——和为 K 的子数组
算法·leetcode·职场和发展
zl_vslam4 小时前
SLAM中的非线性优-3D图优化之李群李代数在Opencv-PNP中的应用(四)
人工智能·opencv·算法·计算机视觉
Run_Teenage7 小时前
C++:智能指针的使用及其原理
开发语言·c++·算法
mit6.8248 小时前
二维差分+前缀和
算法
民乐团扒谱机8 小时前
自然的算法:从生物进化到智能优化 —— 遗传算法的诗意与硬核“
算法