本文先介绍什么是单调栈,然后用一个简单的示例进行分析以加强理解,最后以十四届蓝桥杯省赛第四题"最大矩形纸片"为案例,分析多种不同的实现。
什么是单调栈
单调栈是一种常用的数据结构,主要用于解决需要维护一组元素的单调性问题,尤其是在求解“下一个更大元素”、“下一个更小元素”这类问题时,效率非常高,通常可以在线性时间内完成。单调栈的特点如下:
1. 单调性
单调栈可以分为两种类型:
- 单调递增栈:栈中的元素从栈底到栈顶保持递增,即越靠近栈顶的元素越大。
- 每当一个新元素小于栈顶元素时,栈顶元素会被弹出。
- 单调递减栈:栈中的元素从栈底到栈顶保持递减,即越靠近栈顶的元素越小。
- 每当一个新元素大于栈顶元素时,栈顶元素会被弹出。
注意:这里的“递增”或“递减”通常是指栈中存储的实际元素的值,但在某些问题中,也可以与这些元素的索引等信息结合使用。
2. 动态维护区间信息
单调栈擅长处理动态维护区间内的某种极值问题,例如:
- 找到数组中每个元素的最近的比它大或比它小的元素。
- 在不使用嵌套循环的情况下高效处理“下一更大/更小元素”的问题。
由于栈中的元素始终保持某种单调性,当遍历到一个新的元素时,可以通过不断弹出栈顶元素的方式来找到符合条件的区间边界。
3. 时间复杂度
单调栈的时间复杂度是 O(n)。虽然每个元素会被压入和弹出栈,但每个元素最多只会进栈和出栈一次,因此整体操作是线性的,非常高效。
4. 应用场景
单调栈广泛应用于很多经典的算法问题中,特别是需要快速找到某个元素在特定方向上的最大或最小值的情况。常见的应用包括:
- 下一个更大元素问题(Next Greater Element)
- 下一个更小元素问题(Next Smaller Element)
- 直方图中最大矩形问题(基于单调栈找出最大的矩形面积)
- 滑动窗口极值问题(滑动窗口中找出最大或最小值)
5. 栈操作与逻辑
单调栈的操作逻辑通常包括以下几个步骤:
- 遍历数组:按顺序遍历数组的每一个元素。
- 弹出栈顶元素:如果当前元素不符合栈的单调性(例如,对于单调递增栈,当前元素小于栈顶元素),则弹出栈顶元素,直到栈顶元素符合单调性为止。
- 处理弹出元素:通常在弹出栈顶元素时,可以得到一些问题的解(如弹出元素对应的最近更大或更小的元素)。
- 压入新元素:将当前元素压入栈,继续遍历下一个元素。
例子:单调递增栈的简单示例
假设我们需要找到数组中每个元素的下一个更大元素:
vector<int> nextGreaterElement(vector<int>& nums) {
vector<int> result(nums.size(), -1); // 初始化结果数组,默认值为 -1
stack<int> s; // 单调递增栈,存储元素的索引
for (int i = 0; i < nums.size(); i++) {
// 当前元素比栈顶元素大,则弹出栈顶并处理
while (!s.empty() && nums[i] > nums[s.top()]) {
result[s.top()] = nums[i]; // 当前元素是栈顶元素的下一个更大元素
s.pop();
}
// 将当前元素的索引压入栈
s.push(i);
}
return result; // 返回结果数组
}
总结
单调栈的主要特点:
- 保持栈中元素的单调性(递增或递减)。
- 高效地处理某个元素的下一个更大或更小值问题。
- 每个元素最多入栈和出栈一次,因此时间复杂度是 O(n)。
单调栈在操作过程中通过维护栈内元素的单调性来高效解决问题。为了说明单调栈的操作过程,下面通过一个经典的“下一个更大元素”问题为例,详细展示单调栈的每一步操作。
示例分析:找出每个元素的下一个更大元素
问题描述:
给定一个数组 nums
,对于数组中的每个元素,找出其右侧的第一个比它大的元素。如果不存在,则返回 -1。我们可以使用单调栈来高效解决这个问题。
例如,给定数组 nums = [2, 1, 5, 3, 6]
,我们想找到每个元素右边的第一个更大元素:
- 对于
2
,右边第一个比2
大的元素是5
。 - 对于
1
,右边第一个比1
大的元素是5
。 - 对于
5
,右边第一个比5
大的元素是6
。 - 对于
3
,右边第一个比3
大的元素是6
。 - 对于
6
,右边没有比6
大的元素,返回-1
。
解决思路:单调递减栈
我们使用单调递减栈(栈中存储的元素从栈底到栈顶递减),在遍历过程中保持栈内元素的递减顺序。这样,当当前元素比栈顶元素大时,栈顶元素就是需要更新的元素,其下一个更大元素就是当前元素。
步骤展示
- 初始化栈和结果数组:
- 使用一个栈
stack
来存储元素的索引。 - 结果数组
result
初始化为 -1,表示找不到下一个更大元素的情况。
- 使用一个栈
vector<int> nums =