单调递减栈中每个元素在弹栈时,其右侧是右边第一个大于自己的元素,左侧是左边第一个大于自己的元素,也就是数组所存在的低谷处。
1、接雨水
当数值低洼处会积累雨水,左侧第一个大于自己的数和右侧第一个大于自己的数对当前数值决定了该位置的雨水量。因此使用单调递减栈,弹栈时累加每个位置的雨水量即可。
class Solution {
public:
int trap(vector<int>& height) {
stack<int> st;
int ans = 0;
for (int i = 0; i < height.size(); ++i) {
while (st.size() && height[i] > height[st.top()]) {
int cur = height[st.top()];
st.pop();
if (st.empty()) {
break; //开始处没雨水
}
int left = st.top();
ans += (min(height[left], height[i]) - cur) * (i - left - 1);
}
st.push(i);
}
return ans;
}
};
2、直方图最大矩形面积 84
面积由底*高决定,对每个小矩形向两侧扩展,遇到比自己矮的就停止,遍历所有位置计算扩展的最大面积。因此扩展的宽由左侧第一个小于自己的数和右侧第一个小于自己的数决定(途中红色矩形,因为和左右两侧高于自己的柱状图之间矩形高仍是当前数值)。因此符合单调递增栈,弹栈时计算出每个位置的矩形面积取最大值即可。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int ans = 0;
stack<int> st;
heights.push_back(0); //为了让占中元素全部弹出,在数组最后添加0
for (int i = 0; i < heights.size(); ++i) {
while (!st.empty() && heights[st.top()] > heights[i]) {
int h = heights[st.top()];//为什么用该位置做矩形高呢,因为左右第一个低于自己的区间内,都高于自己
st.pop();
int area = 0;
if (st.empty()) area = i * h;
else area = (i - st.top()-1) * h;
ans = max(ans, area);
}
st.push(i);
}
return ans;
}
};
3、 验证前序遍历序列二叉搜索树"题解:225
先序遍历搜索树,只有左子树时元素是单调递减的,但存在某一结点的右子树就不满足单调。如
5
/ \
2 6
/ \
1 3
输入: [5,2,6,1,3]
输出: false
先序遍历[5,2,1,3,6]。只有左子树[5,2,1],加入2的右子树变成[5,2,1,3]。因此为了保持栈的单调,元素大于栈顶元素,弹出栈中小于当前结点的值,并把当前节点入栈,变成[5,3],这时栈中又只剩下左子树,满足单调。最后一个弹出栈2的是当前节点3的根节点。我们使用root记录根节点的值,因为程序中当前节点总在根节点右边,所以当前节点小于根节点就是非法。
class Solution {
public:
bool verifyPreorder(vector<int>& preorder) {
if(preorder.empty()) return true;
stack<int> st;
int root = INT_MIN;
for(int num : preorder){
if(num <root)
return false;
while(!st.empty() && num > st.top()){
root = st.top();
st.pop();
}
st.push(num);
}
return true;
}
};
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( l o g n ) O(logn) O(logn)。如果使用递归时间复杂度 O ( n 2 ) O(n^2) O(n2),时间超时。递归伪代码:
bool verifyPreordCore(vector<int>& preorder, int start,int end){
if(start > end) return true;
int root = preorder[start], littleRange = start + 1;
while(littleRange <= end && preorder[littleRange] < root)
littleRange ++;
int i = littleRange;
while(i<= end){
if(preorder[i] < root)
return false;
i++;
}
return verifyPreordCore(preorder, start+1, littleRange-1) &&
verifyPreordCore(preorder, littleRange, end);
}
4、移除k位后的最小数字
移除k位后的数字位数是一定的n-k,最小数字也就是尽量让高位放小的数,因此移除大于当前数的之前数字,让当前数字递补到前几位,因此符合单调递增队列。
class Solution {
public:
string removeKdigits(string num, int k) {
stack<int> st;
for (int i = 0; i < num.size(); ++i) {
//总共移除k个数字。
while (st.size() && num[i] < num[st.top()] && k > 0) {
st.pop();
--k;
}
st.push(i);
}
//可能存在栈中元素没移除玩的情况
while (st.size() && k > 0) {
st.pop();
--k;
}
string ans;
while(st.size()) {
ans += num[st.top()];
st.pop();
}
reverse(ans.begin(), ans.end());
//去掉前导0
int i = 0;
for (;i < ans.size() && ans[i] == '0'; ++i);
return i == ans.size() ? "0" : ans.substr(i);
}
};