算法常见题型之STL 栈进阶:单调栈

发布时间:2026/8/2 8:05:05
算法常见题型之STL 栈进阶:单调栈 STL 栈进阶单调栈基本用法与 例题题解一、单调栈基础1. 核心定义单调栈是普通栈的进阶结构其核心特征是栈内元素始终保持严格的单调性单调递增或单调递减。通过在元素入栈时主动弹出破坏单调性的元素单调栈可以在线性时间内解决一系列区间边界查找问题。2. 经典应用场景单调栈最核心的能力是在 O(n) 时间内为数组中每个元素找到左侧/右侧第一个比它更大/更小的元素。3. 通用模板寻找左侧第一个更大元素以维护严格单调递增栈、查找每个元素左侧第一个更大元素为例模板如下vectorintgetLeftGreater(vectorintarr){intnarr.size();vectorintres(n,-1);stackintst;// 存储元素下标for(inti0;in;i){// 弹出所有不大于当前元素的栈顶保持栈递增while(!st.empty()arr[st.top()]arr[i]){st.pop();}if(!st.empty())res[i]st.top();st.push(i);}returnres;}时间复杂度O(n)每个元素有且仅有一次入栈、出栈操作。空间复杂度O(n)最坏情况下栈存储全部元素。二、ABC359E Water Tank 题目分析[AT_abc359_e ABC359E] Water Tank - 洛谷1. 题目大意给定一个长度为N NN的正整数序列H HH代表水箱中N NN块隔板的高度。隔板将水箱分为 (N1) 个隔间A 0 ∼ A N A_0 \sim A_NA0​∼AN​初始所有隔间水量为 0。重复执行以下操作向最左侧隔间A 0 A_0A0​加入 1 单位水从左到右遍历每块隔板若左侧隔间水量 右侧隔间水量且左侧水量 隔板高度则向右侧转移 1 单位水。求每个隔间A i A_iAi​首次水量大于 0 时对应的操作次数。2. 样例直观理解以样例输入5 3 1 4 1 5为例前 3 次操作水全部积存在A 0 A_0A0​中水位最高为 3未超过第一块隔板高度 3右侧始终无水。第 4 次操作A 0 A_0A0​水位升至 4超过隔板 1 高度1 单位水流向A 1 A_1A1​A 1 A_1A1​首次大于 0对应答案 4。第 5 次操作再次注水后A 1 A_1A1​水位升至 2超过隔板 2 高度 11 单位水流向A 2 A_2A2​A 2 A_2A2​首次大于 0对应答案 5。3. 模型转化连通器注水我们可以将问题等价为从左向右匀速注水的连通器模型高隔板相当于“大坝”水位低于大坝时被阻挡无法向右流动水位超过大坝后左右连通水位同步上涨。两块高隔板之间形成独立“水槽”水槽的最高蓄水高度由较矮的一侧隔板决定木桶效应。隔间A i A_iAi​首次有水的时刻等价于水刚好漫过第i ii块隔板的时刻。我们定义ans[i]第i ii块隔板左侧水位恰好齐平隔板高度时的总注水量。此时水刚好要漫过隔板再注入 1 单位水就会流入A i A_iAi​因此最终答案为ans[i] 1。三、算法推导单调栈递推求解1. 递推公式推导对于第i ii块隔板我们通过单调栈找到左侧第一个高度严格大于 (h[i]) 的隔板j jj基于此推导注水量左边界意义j jj是左侧第一个比i ii高的“大坝”j jj左侧的水量积累与i ii无关齐平j jj隔板的总水量为ans[j]。水槽容量j jj到i ii之间共有i − j i-ji−j个隔间A j ∼ A i − 1 A_j \sim A_{i-1}Aj​∼Ai−1​形成一个独立水槽。左边界j jj更高右边界i ii是短板因此水槽高度为 (h[i])。水量计算将水槽填满至 (h[i]) 高度齐平i ii隔板新增水量 隔间数 × 高度 (i-j) *(h[i])。递推式ans[i] ans[j] (i - j) * (h[i])2. 边界处理如果i ii是当前遍历过的最高隔板左侧无更高隔板则虚拟一个 0 号左边界高度无穷大即 (j0)且约定ans[0] 0。此时前i ii个隔间形成一个完整水槽总水量为 i *(h[i])与递推式一致。3. 算法步骤初始化空栈维护严格单调递增的隔板下标从左到右遍历每块隔板i ii弹出栈顶所有高度 ≤ (h[i]) 的元素保证栈的单调性若栈为空则左边界 (j0)否则j jj为栈顶元素左侧第一个更高隔板按递推式计算ans[i]输出ans[i] 1将当前隔板下标i ii压入栈中。4. 复杂度分析时间复杂度O(N)。每块隔板最多入栈、出栈各一次总操作数与数据规模线性相关。空间复杂度O(N)。栈与 ans 数组均占用线性空间。5. 数据范围提示H i H_iHi​最大可达10 9 10^9109N NN最大为2 × 10 5 2\times10^52×105因此ans[i]最大值可达2 × 10 14 2\times10^{14}2×1014超出 32 位整数范围必须使用 64 位整数long long存储避免溢出。四、完整代码与注释#includebits/stdc.h#defineintlonglong// 全局使用long long防止数值溢出usingnamespacestd;constintN2e59;inth[N],ans[N];signedmain(){ios::sync_with_stdio(false);cin.tie(nullptr);// 加速输入输出intn;cinn;for(inti1;in;i){cin\(h[i]\);}stackintst;// 单调递增栈存储隔板下标for(inti1;in;i){// 弹出所有高度不大于当前隔板的栈顶维护单调性while(!st.empty()h[st.top()]\(h[i]\)){st.pop();}intj;if(st.empty()){j0;// 左侧无更高隔板取虚拟边界0}else{jst.top();// 左侧第一个高度严格大于当前的隔板}// 递推计算齐平当前隔板的总水量1为首次漫过的时刻ans[i]ans[j](i-j)*\(h[i]\);coutans[i]1 ;st.push(i);}return0;}五、思路总结本题是单调栈的经典应用场景核心在于将“注水时间”问题转化为“寻找左侧第一个更大元素”的经典模型利用单调栈高效定位每块隔板的左侧“大坝”确定独立水槽的边界通过递推累加每个水槽的注满时间得到每个隔板的溢水时刻。这种思路避免了暴力模拟的高复杂度完美适配2 × 10 5 2\times10^52×105的数据规模是单调栈解决区间边界问题的典型范例。