进阶数据结构——单调栈

在这里插入图片描述

一、 基本概念

单调栈是一种特殊的栈结构,栈内元素保持单调递增或递减。常用于解决需要找到元素左侧或右侧第一个比它大或小的元素的问题。

二、 单调栈的类型

单调递增栈:栈内元素从栈底到栈顶递增。
单调递减栈:栈内元素从栈底到栈顶递减。

三、应用场景

下一个更大元素:找到数组中每个元素右侧第一个比它大的元素。
下一个更小元素:找到数组中每个元素右侧第一个比它小的元素。
最大矩形面积:在直方图中找到最大矩形面积。
滑动窗口最大值:在滑动窗口中找到最大值。

四、 实现步骤

以下一个更大元素为例:

  1. 初始化一个空栈。
  2. 遍历数组中的每个元素:
    ◦ 当栈不为空且当前元素大于栈顶元素时,弹出栈顶元素并记录当前元素为栈顶元素的下一个更大元素。
    ◦ 将当前元素压入栈中。
  3. 遍历结束后,栈中剩余元素没有下一个更大元素。

五、 复杂度分析

时间复杂度:O(n),每个元素最多入栈和出栈一次。
空间复杂度:O(n),栈的大小最多为n。

六、 注意事项

确保栈的单调性,根据问题需求选择递增或递减栈。
处理边界条件,如空栈或数组末尾元素。

七、代码模版(c++)

#include<iostream>
#include<stack>
using namespace std;

#define maxn 50001
#define inf 2000000000

template<typename T>
bool cmp(T a, T b) {
	return a >= b;
}

template<typename T>
void findFirstGreaterOnLeft(int n, T h[], int ans[]) {
	h[0] = inf;
	stack<int>stk;
	stk.push(0);
	for (int i = 1; i <= n; i++) {
		while ( !cmp(h[stk.top()], h[i])) {
			stk.pop();
		}
		ans[i] = stk.top();
		stk.push(i);
	}
	
}

template<typename T>
void reverseArray(int n, T arr[]) {
	for (int i = 1; i <= n / 2; i++) {
		T t = arr[i];
		arr[i] = arr[n - i + 1];
		arr[n - i + 1] = t;
	}
}

int h[maxn], ans[maxn];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> h[i];
	}
	findFirstGreaterOnLeft(n, h, ans);
	for (int i = 1; i <= n; i++) {
		cout << ans[i] << ' ';
	}
	cout << endl;
	
	return 0;
}

八、经典例题

伦太郎的等待

#include<iostream>
#include<stack>
using namespace std;

#define maxn 100001
#define inf 2000000000

template<typename T>
bool cmp(T a, T b) {
	return a >= b;
}

template<typename T>
void reverse(int n, T h[]) {
	for (int i = 1; i <= n / 2; i++) {
		T t = h[i];
		h[i] = h[n - i + 1];
		h[n - i + 1] = t;
	}
}

template<typename T>
void findFirstGreaterOnLeft(int n, T h[], int ans[]) {
	stack<int>stk;
	stk.push(0);
	h[0] = inf;
	for (int i = 1; i <= n; i++) {
		while (!cmp(h[stk.top()], h[i])) {
			stk.pop();
		}
		ans[i] = stk.top();
		stk.push(i);
	}
}

double h[maxn];int ans[maxn];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> h[i];
	}
	findFirstGreaterOnLeft(n, h, ans);
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		cnt += i - 1 - ans[i];
	}
	cout << cnt << endl;

	return 0;
}

百亿富翁

#include<iostream>
#include<stack>
using namespace std;

#define maxn 700001
#define inf 2000000000

template<typename T>
bool cmp(T a, T b) {
	return a > b;
}

template<typename T>
void reverseArr(int n, T h[]) {
	for (int i = 1; i <= n / 2; i++) {
		T t = h[i];
		h[i] = h[n - i + 1];
		h[n - i + 1] = t;
	}
}

template<typename T>
void findFirstGreaterOnLeft(int n, T h[], int ans[]) {
	stack<int>stk;
	stk.push(0);
	h[0] = inf;
	for (int i = 1; i <= n; i++) {
		while (!cmp(h[stk.top()], h[i])) {
			stk.pop();
		}
		ans[i] = stk.top();
		stk.push(i);
	}
}

double h[maxn];int ans[maxn];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> h[i];
	}
	findFirstGreaterOnLeft(n, h, ans);
	for (int i = 1; i <= n; i++) {
		if (ans[i] == 0)ans[i] = -1;
		cout << ans[i] << ' ';
	}
	cout << endl;
	reverseArr(n, h);
	findFirstGreaterOnLeft(n, h, ans);
	reverseArr(n, ans);
	for (int i = 1; i <= n; i++) {
		if (ans[i] == 0)ans[i] = -1;
		else ans[i] = (n + 1) - ans[i];
		cout << ans[i] << ' ';
	}
	cout << endl;
	return 0;
}

最大区间

#include<iostream>
#include<stack>
using namespace std;

#define maxn 300001
#define inf -1

template<typename T>
bool cmp(T a, T b) {	//题目要求(R-L+1)*a[i]尽可能求最大值
	return a < b;		//因为题目要求说尽可能求得最大值,那么我们就找左边和右边第一个小的值,再把它们去掉
}

template<typename T>		//反转数组的目的就是为了找右边第一个大的值
void reverseArr(int n, T h[]) {
	for (int i = 1; i <= n / 2; i++) {
		T t = h[i];
		h[i] = h[n - i + 1];
		h[n - i + 1] = t;
	}
}

template<typename T>
void findFirstGreaterOnLeft(int n, T h[], int ans[]) {
	stack<int>stk;
	stk.push(0);
	h[0] = inf;
	for (int i = 1; i <= n; i++) {
		while (!cmp(h[stk.top()], h[i])) {
			stk.pop();
		}
		ans[i] = stk.top();
		stk.push(i);
	}
}

double h[maxn]; int l[maxn], r[maxn];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> h[i];
	}
	findFirstGreaterOnLeft(n, h, l);
	reverseArr(n, h);
	findFirstGreaterOnLeft(n, h, r);
	reverseArr(n, h);
	reverseArr(n, r);
	for (int i = 1; i <= n; i++) {
		r[i] = n + 1 - r[i];
	}
	long long max = 0;		
	for (int i = 1; i <= n; i++) {
		long long x = (r[i] - 1) - (l[i] + 1) + 1;		//把这两个值去掉后就是要找的范围
		x = x * h[i];
		if (x > max)max = x;
	}
	cout << max << endl;
	
	return 0;
}

九、总结

单调栈是一种高效的数据结构,适用于解决特定类型的问题。通过维护栈的单调性,可以快速找到元素之间的关系,提升算法效率。

在这里插入图片描述
希望大家可以一键三连,后续我们一起学习,谢谢大家!!!
在这里插入图片描述

<think>嗯,用户想了解单调栈的实现和应用场景。从引用资料来看,之前已经提供了相关说明,但用户可能希望更系统化的总结。用户可能是算法学习者,正在准备面试或刷题,需要清晰且可直接应用的实例。注意到引用[1][2][3]都强调了单调栈的核心特点:保持栈内元素单调性,用于高效查找邻近更大/更小元素。引用[4][5]给出了经典题目(柱状图最大矩形)的代码实现,这些可以拆解说明。实现部分要突出三点:单调性维护(递增/递减栈)、出栈操作的含义(当前元素触发计算)、存储索引而非值的原因(方便计算边界)。应用场景需要区分典型问题类型:边界搜索、面积优化、跨度计算。用户可能特别关注“为什么用栈而不用其他结构”——需要解释后进先出特性与“最近邻”问题的适配性。代码示例选用C++和Python双版本,因为引用[4]是C++而用户历史问题含Python代码。注意在解释部分变量时保持一致性(如stk数组的top指针)。性能要明确O(n)的原因,避免用户误解退化情况。用户没提语言偏好,但上次回复含Python代码,所以优先Python示例。应用场景补充“矩阵最大矩形”的转化思路(引用[4]标题提到),这是单调栈进阶用法,能展示该算法的扩展性。</think> 单调栈是一种特殊的数据结构,其核心在于**维护栈内元素的单调性**(单调递增或递减)。它能够高效解决一系列需查找元素“邻近边界”的问题,以下从实现和应用场景两方面详细说明: --- ### **一、单调栈实现** #### **核心思路** 1. **遍历顺序**:通常从左到右遍历数组 2. **栈内维护**:保持栈底到栈顶的单调性(增/减) 3. **出栈条件**:当新元素破坏单调性时,弹出栈顶元素并计算相关解 #### **代码示例(Python)** ```python def monotonic_stack(nums): stack = [] # 存储数组索引(通常比存值更灵活) res = [-1] * len(nums) # 初始化结果数组(例如存右侧第一个更大元素) for i in range(len(nums)): # 当栈非空且当前元素 > 栈顶元素(递减栈则用 <) while stack and nums[i] > nums[stack[-1]]: idx = stack.pop() # 弹出栈顶索引 res[idx] = nums[i] # 当前元素即为栈顶元素的"右侧更大元素" stack.append(i) # 当前元素索引入栈 return res # 示例:输入 [3, 1, 4, 2] # 输出: [4, 4, -1, -1] 表示:3右侧第一个大的是4,1右侧第一个大的是4,4和2右侧无更大元素 ``` #### **关键点** - **时间复杂为 $O(n)$**:每个元素最多入栈、出栈一次 - **栈中存储索引**:便于计算距离、区间宽度等 - **单调性选择**: - **递减栈**:找右侧第一个更大元素 - **递增栈**:找右侧第一个更小元素(如柱状图最大矩形) --- ### **二、经典应用场景** #### **1. 边界查找问题** - **下一个更大/小元素**(Next Greater Element) - 例:`[2,1,2,4,3]` → 下一个更大元素 `[4,2,4,-1,-1]` [^1][^5] - **左侧第一个更大/小元素**(类似反向遍历) #### **2. 面积优化问题** - **柱状图中最大矩形**(LeetCode 84)[^4] - **核心代码片段**(递增栈计算宽度): ```python stack = [-1] # 哨兵节点 max_area = 0 for i in range(len(heights)): while stack[-1] != -1 and heights[stack[-1]] > heights[i]: h = heights[stack.pop()] w = i - stack[-1] - 1 # 宽度 = 当前索引 - 新栈顶索引 - 1 max_area = max(max_area, h * w) stack.append(i) ``` - **最大矩形(01矩阵)**(LeetCode 85)[^4] - 转化为逐行的柱状图问题 #### **3. 跨度计算问题** - **每日温度**(LeetCode 739)[^5] - 求等待更暖天数的间隔:`[73,74,75,71,69,76]` → `[1,1,4,2,1,0]` - 解法:递减栈记录温度下标,出栈时计算天数差 #### **4. 删除重复字母(字典序最小)** - **关键点**:维护递增栈,处理字符最后出现位置 [^2] --- ### **单调栈适用条件** - 问题需找到**某一侧最近的边界元素**(更大/更小) - 计算过程需要**利用弹出元素的中间结果**(如面积、距离) - 数组遍历方向与边界查找方向一致(左→右找右边界,右→左找左边界) > 注:实际应用中,80% 单调栈问题可归结为以上场景。理解“单调性维护”和“边界触发计算”两个核心即可快速识别解法[^3][^5]。 --- **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值