无重复元素的单调栈
- 保证元素在入栈的过程中,做到任意时刻从栈底到栈顶的元素都是单调(递增或者递减)的,那么此种栈就是一个单调栈。
- 单调栈的性质:
- 利用其找到数组中左边最近且小于当前元素的元素和右边最近且小于当前元素的元素
- 利用其找到数组中左边最近且大于当前元素的元素和右边最近且大于当前元素的元素
- 单调栈找到小于(大于)且最近的元素的原理:
- 栈中存的是数组元素的下标
- 递增单调栈中(栈底到栈顶按照从小到大的顺序)
- 当前元素如果大于栈顶,或者栈为空 入栈
- 当前元素如果小于栈顶,一直退栈直到当前元素大于栈顶才能入栈
- 退栈栈顶元素的时候,此时就是计算此退栈元素的左小且最近和右小且最近的时刻
- 让其退栈的右边元素就是其右小且最近,若无则是-1(无效下标)
- 其退栈后,新的栈顶就是其左小且最近,若无则是-1(无效下标)
- 最后将所有元素遍历完成后,必须按照同样的方法去将栈中剩余元素全部退栈掉
void getNearNums(const vector<int>& nums, vector<pair<int, int>>& nearMinNums)
{
stack<int> stIdx; // 单调栈
for (int i = 0; i < nums.size(); ++i) {
if (stIdx.empty() || nums[i] > nums[stIdx.top()]) {
stIdx.push(i); // 存下标
} else {
//栈不为空,跟栈顶比较,只要这个栈顶还大于当前元素就要继续退栈
while (!stIdx.empty() && nums[i] < nums[stIdx.top()]) {
int idx = stIdx.top();
stIdx.pop();
// first左小 second右小
nearMinNums[idx].first = stIdx.empty() ? (-1) : (stIdx.top());
nearMinNums[idx].second = i;
}
// 最后必须将当前元素给入栈
stIdx.push(i);
}
}
// stIdx还有元素
while (!stIdx.empty()) {
int idx = stIdx.top();
stIdx.pop();
nearMinNums[idx].first = stIdx.empty() ? (-1) : (stIdx.top());
nearMinNums[idx].second = -1;
}
}
有重复元素的单调栈
- 基本的思路跟上面的单调栈还是相同的,不过有一些区别
- 入栈时,栈中只有一个指针指向一个链表,链表中按照从头到尾的顺序在尾部插入相同值的元素的数组中的下标
- 出栈时,左小且最近元素的下标为新栈顶的链表的最右元素的下标 或 -1
- 特别注意, 弹栈的时候弹栈的是整个链表,然后在整个链表上进行一个遍历
- 同样的,弹栈的时刻就是当前被弹栈元素的计算时刻。
单调栈实战
直方图的最大矩形问题
题目连接
思路:
最大的那个面积肯定是取决于某个最低的高度值,必然是某个高度值。因此,我们只需要计算出每个高度能得到的最大面积,然后找到最大的面积就是最终答案。如何求每个矩形能达到的最大面积呢?利用递归栈可以分别找到左、右最小最近的元素的下标,因此相减不就是当前矩形的最大宽度吗!
class Solution {
public:
// 最大的那个面积肯定是取决于某个最低的高度值,必然是某个高度值,
// 因此,我们只需要计算出每个高度能得到的最大面积,然后找到最大的面积就是最终答案
unsigned long long largestRectangleArea(vector<int>& heights) {
long maxArea = 0;
stack<int> stIdx;
for (int i = 0; i < heights.size(); ++i) {
if (stIdx.empty() || heights[stIdx.top()] <= heights[i]) {
stIdx.push(i);
} else if (heights[stIdx.top()] > heights[i]) {
while (!stIdx.empty() && heights[stIdx.top()] > heights[i]) {
// 弹栈
int index = stIdx.top();
stIdx.pop();
// 计算面积
int height = heights[index];
// 宽度就是最右边的那个近小值-最左边的近小值
int width = (i) - (stIdx.empty() ? (-1) : (stIdx.top())) - 1;
unsigned long long area = height * width;
maxArea = area > maxArea ? area : maxArea;
}
stIdx.push(i);
}
}
while (!stIdx.empty()) {
int index = stIdx.top();
stIdx.pop();
// 计算面积
int height = heights[index];
// 宽度就是最右边的那个近小值-最左边的近小值
int width = (heights.size()) - (stIdx.empty() ? (-1) : (stIdx.top())) - 1;
long area = height * width;
maxArea = area > maxArea ? area : maxArea;
}
return maxArea;
}
};
参考资料
1 https://blog.youkuaiyun.com/weixin_40374341/article/details/100055210
2 https://blog.youkuaiyun.com/weixin_40374341/article/details/100065437