1 简介
- 单调栈:每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)
- 使用场景:找第一个大于他的元素,第一个小于他的元素。左右边界都可以。
2 例题
2.1 下一个更大元素
题目
- leetcode496
- 给你一个数组,返回一个等长的数组,对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。
分析
- 找第一个大于自己的数,首先找肯定是往后找,所以每次判断入栈之前要判断栈中的数据有没有大于自己的,栈中的数据肯定是存的是后面的数据,涉及到遍历顺序-》肯定是从后往前遍历
- 如何保证栈顶是第一个大于自己的树??元素入栈之前判断是否大于该元素,如果小于等于那就出栈呗,直到第一个大于他的栈顶元素,就找到了,如果没有就返回-1
- 找没找到都要把该节点放入栈中,他就是该栈的最小元素,也可能是下个元素需要的第一大元素。
代码
vector<int> nextGreaterElement(vector<int>& nums) {
vector<int> res(nums.size()); // 存放答案的数组
stack<int> s;
// 倒着往栈里放
for (int i = nums.size() - 1; i >= 0; i--) {
// 判定个子高矮
while (!s.empty() && s.top() <= nums[i]) {
// 矮个起开,反正也被挡着了。。。
s.pop();
}
// nums[i] 身后的 next great number
res[i] = s.empty() ? -1 : s.top();
//
s.push(nums[i]);
}
return res;
}
2.2 再过多少天升温
题目
- leetcode1118题
- 给你一个数组T,这个数组存放的是近几天的天气气温,你返回一个等长的数组,计算:对于每一天,你还要至少等多少天才能等到一个更暖和的气温;如果等不到那一天,填 0。
- 比如说给你输入T = [73,74,75,71,69,76],你返回[1,1,3,2,1,0]。
分析
- 本质上还是找第一大元素,不同是不要返回第一大元素是谁,而是距离第一大元素的距离
- 因为要获取索引,所以单调栈中要存放的是索引值。
代码
vector<int> dailyTemperatures(vector<int>& T) {
vector<int> res(T.size());
// 这里放元素索引,而不是元素
stack<int> s;
/* 单调栈模板 */
for (int i = T.size() - 1; i >= 0; i--) {
while (!s.empty() && T[s.top()] <= T[i]) {
s.pop();
}
// 得到索引间距
res[i] = s.empty() ? 0 : (s.top() - i);
// 将索引入栈,而不是元素
s.push(i);
}
return res;
}
2.3 环形数组的下一个更大元素
问题
- 同样是 Next Greater Number,现在假设给你的数组是个环形的,如何处理?力扣第 503 题「下一个更大元素 II」
- 比如输入一个数组[2,1,2,4,3],你返回数组[4,2,4,-1,4]。拥有了环形属性,最后一个元素 3 绕了一圈后找到了比自己大的元素 4。
分析
- 这个问题肯定还是要用单调栈的解题模板,但难点在于,比如输入是[2,1,2,4,3],对于最后一个元素 3,如何找到元素 4。
- double长度数组,对于结果的res数组进行两遍赋值就能找到3后面的4了。
代码
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;
}
2.4 柱状图中最大的矩形
题目
- leetcode84题
- 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
- 输入: [2,1,5,6,2,3] 输出: 10
分析
- 不错的题解
- 这个题让我想到了不同长度模板的最大盛水问题,但是仔细一想又不同,首先盛水问题宽度是由两边界的差值决定,高度由两边界的最小值来决定,和中间模板的长度没有什么关系。但是该题的最大矩形面积,面积是连续,一块面积的高度是由该矩形中的最小的柱状图来决定的,宽度是由小于该高度的左边第一个index和小于该高度的右边第一个index的差值决定的。
- 考虑使用单调栈的可行性分析:如果使用单调递增栈,那么对于index位置的柱状图,第一个小于该高度的左边第一个元素肯定是存放在栈中的下一个元素(相对于第一个大元素,则是第一小元素),第一个小于该高度右边的第一个元素肯定是入栈元素位置,因为存在栈中的肯定比他要大。宽度就得到了,高度则是该index的高度。
- 计算宽度要用索引,单调栈中肯定是存放的是索引。
代码
class Solution {
public int largestRectangleArea(int[] heights) {
int res = 0;
//存索引好计算宽度
Stack<Integer> stack = new Stack<>();
// 将给定的原数组左右各添加一个元素0,防止第一个元素的索引越界和访问到最后一元素
int[] newHeights = new int[heights.length + 2];
newHeights[0] = 0;
newHeights[newHeights.length-1] = 0;
for (int i = 1; i < heights.length + 1; i++) {
newHeights[i] = heights[i - 1];
}
for(int i = 0;i<newHeights.length;i++){
//小于栈顶考虑出站,出栈结点的计算
//计算面积因为是单调栈,所以当前结点为高的矩形的宽只要往右边算就好了
while(!stack.isEmpty() && newHeights[i]<newHeights[stack.peek()]){
int index = stack.pop();
int height = newHeights[index];
//存放索引可能不连续
int kuan = i - stack.peek()-1;
res = Math.max(res,height*kuan);
}
//大于等于正常入栈
stack.push(i);
}
return res;
}
}
2.5 接雨水
题目
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6 解释:上面是由数组
[0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
分析
- 这题完全可以用双指针来做,从两端往中间扫描,默认右边大,左边小往中间扫描,左边的小高度减去相应的底就是该位置的盛水量,直到左右指针相遇。
- 这题也可以用单调栈来做。分析:什么时候开始计算雨水:当某一个位置的两端高度大于该高度的时候就可以盛雨水。即找到第一个大于他的就开始计算面积。
- 什么时候正常入栈呢,那就是没有找到大于他的,所以是个单调递减的栈
- 当遇到一个比栈顶大的元素的时候,考虑计算该位置的面积。高度:左右两边的最小高度-盛水的底部高度,宽度:左右两边的矩形差值。注意底部高度只能使用一次,使用完要pop。
代码
双指针解法
public int trap(int[] height) {
int left = 0, right = height.length - 1;
int ans = 0;
int left_max = 0, right_max = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= left_max) {
left_max = height[left];
} else {
ans += (left_max - height[left]);
}
++left;
} else {
if (height[right] >= right_max) {
right_max = height[right];
} else {
ans += (right_max - height[right]);
}
--right;
}
}
return ans;
}
单调递减栈
public class ti_17_21直方图水量 {
public int trap(int[] height) {
Stack<Integer> stack = new Stack<>();
int res =0;
for(int i =0 ;i<height.length;i++){
while(!stack.isEmpty() && height[i]>height[stack.peek()]){
int top = stack.pop();//盛水的水底,用过了就要pop出来
if(stack.isEmpty()){//栈中没有元素没有底了
break;
}
int left = stack.peek();
int len = i -left-1;
int heightTemp = Math.min(height[i],height[left])-height[top];
res+=len*heightTemp;
}
stack.push(i);
}
return res;
}
}