一 什么是单调栈?
顾名思义,单调栈就是栈内元素是单调上升/下降的。
二 单调栈的应用
通过入栈和出栈的循序信息可以得到,该入栈和出栈的相对大小关系。进而得到:
栈顶元素左右两边第一个比他大/小的值。
注意!!!只有栈顶元素的左右两边才能确定。在思考的时候总是以栈顶元素为主。
三 小技巧
在输入数组的末尾加入一个最值,可以使得在一次遍历之后,栈内为空。这样不需要单独处理边界问题。
单调上升栈 ——》 找栈顶元素左右两边第一个比他小的数
单调下降栈 ——》 找栈顶元素左右两边第一个比他大的数
帮助记忆:找右边比当前大的数,那么右边大的数应该把当前数弹出去,所以小于当前值的放进到栈里,就是单调下降栈了。
首先要结合问题考虑需要用哪个栈。
四 单调栈模板:
可以直接存成pair,把index信息也包含进去。
stack<int> st;
for(int i = 0; i < nums.size(); i++)
{
while(!st.empty() && st.top() > nums[i])
{
st.pop();
}
st.push(nums[i]);
}
五 例题
遍历以栈顶元素为高的所有可能最大矩形。所以找到两边比栈顶元素小的边界,就可以得到以栈顶元素做高的最大矩形
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
if(heights.empty()) return 0;
int res = 0;
stack<pair<int, int>> MonoStack;
MonoStack.push({heights[0], 0});
heights.push_back(-1);
for(int i=1; i < heights.size(); i ++){
// r = i
// 确定 以栈顶的高度 为标准的 最大面积
while(!MonoStack.empty() && heights[i] < MonoStack.top().first){
int h = MonoStack.top().first;
MonoStack.pop();
if(MonoStack.empty()){
res = max(res, i * h);
}else{
res = max(res,
(i - MonoStack.top().second - 1) * h);
}
}
MonoStack.push({heights[i], i});
}
return res;
}
};
简单题,只考虑右边界就好。
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> cache;
vector<int> res;
stack<int> st;
st.push(0);
nums2.push_back(INT32_MAX);
for(int i = 1; i < nums2.size(); i ++){
while(!st.empty() && nums2[i] > nums2[st.top()]){
int cur = nums2[st.top()];
st.pop();
if(i == nums2.size() - 1){
cache.insert({cur, -1});
}
else{
cache.insert({cur, nums2[i]});
}
}
st.push(i);
}
// for(auto c: cache){
// cout << c.first << ": " << c.second << endl;
// }
for(auto num: nums1){
res.push_back(cache[num]);
}
return res;
}
};
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。
class Solution {
public:
// vector<int> nextGreaterElements(vector<int>& nums) {
// vector<int> ans(nums.size());
// stack<int> s;
// for(int i=nums.size()-1;i>=0;i--){
// s.push(nums[i]);
// }
// for(int i=nums.size()-1;i>=0;i--){
// while(!s.empty() && s.top()<=nums[i]){
// s.pop();
// }
// ans[i]=s.empty()?-1:s.top();
// s.push(nums[i]);
// }
// return ans;
// }
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> res(n); // 存放结果
stack<int> s;
// 假装这个数组长度翻倍了
for (int i = 2 * n - 1; i >= 0; i--) {
while (!s.empty() && s.top() <= nums[i % n])
s.pop();
res[i % n] = s.empty() ? -1 : s.top();
s.push(nums[i % n]);
}
return res;
}
};
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
灌水问题总结:
非常高频可以作为经典原型题记住。
由外向内灌水:
找注水点,根据木桶原理,可以注水的位置是边界的最低位置。
那么下一个位置可以加入的水一定不会超过最低边界,如果下一个位置小于最低边界,则可以注水,如果大于则设置为新的边界。直到左右两指针碰撞,遍历所有位置。
单调栈的做法:
单调栈可以找到盆的左右两边第一个比他大的值,则以该高度进行填充,直到找到所有长方形。
class Solution {
public:
int trap(vector<int>& height) {
if(height.size() <= 2) return 0;
int res = 0;
// int l = 0;
// int r = height.size() - 1;
// int lh = height[l];
// int rh = height[r];
// while(l < r){
// lh = max(lh, height[l]);
// rh = max(rh, height[r]);
// if(lh > rh){
// res += rh - height[r];
// r --;
// }
// else{
// res += lh - height[l] ;
// l ++;
// }
// }
// return res;
int ans = 0;
stack<int> st;
for (int i = 0; i < height.size(); i++)
{
while (!st.empty() && height[st.top()] < height[i])
{
cout << height[i] << ":" << i << endl;
int cur = st.top();
st.pop();
if (st.empty()) break;
int l = st.top();
int r = i;
int h = min(height[r], height[l]) - height[cur];
ans += (r - l - 1) * h;
}
st.push(i);
}
return ans;
}
};
上题的二维拓展,思路一致:
用堆维护一个边界,始终中从边界的最低位置进行注水,注水完成后更新边界。直到所有位置都遍历。
struct ele{
int x;
int y;
int val;
ele(int i, int j, int num): x(i), y(j), val(num){}
};
struct cmp{
bool operator()(const ele &a, const ele &b){
return a.val > b.val;
}
};
class Solution {
public:
int res = 0;
priority_queue<ele, vector<ele>, cmp> minheap;
int row;
int col;
char directions[4][2] = {
{-1, 0},
{1, 0},
{0, -1},
{0, 1}
};
int trapRainWater(vector<vector<int>>& heightMap) {
// init
row = heightMap.size();
col = heightMap[0].size();
if(row <= 2 || col <= 2) return 0;
// 扫描过则遍历为 1
// 堆中保存所有的墙
for(int i = 0; i < row; i ++){
minheap.push(ele(i, 0, heightMap[i][0]));
heightMap[i][0] = -1;
minheap.push(ele(i, col - 1, heightMap[i][col - 1]));
heightMap[i][col - 1] = -1;
}
for(int j = 1; j < col - 1; j ++){
minheap.push(ele(0, j, heightMap[0][j]));
heightMap[0][j] = -1;
minheap.push(ele(row - 1, j, heightMap[row - 1][j]));
heightMap[row - 1][j] = -1;
}
while(!minheap.empty()){
auto inject = minheap.top();
int i = inject.x;
int j = inject.y;
int h = minheap.top().val;
// 扫描过四周则不再需要该点信息
minheap.pop();
for(auto d: directions){
int nexti= i + d[0];
int nextj = j + d[1];
if(nexti < 0 || nexti >= row || nextj < 0 || nextj >= col || heightMap[nexti][nextj] == -1) continue;
if(heightMap[nexti][nextj] < h){
res += h - heightMap[nexti][nextj];
minheap.push(ele(nexti, nextj, h));
}
else{
minheap.push(ele(nexti, nextj, heightMap[nexti][nextj]));
}
heightMap[nexti][nextj] = -1;
}
}
return res;
}
};