1.导论
- 什么是单调栈?
就是栈里的元素保持升序或则降序。
- 什么时候用单调栈
通常是一位数组,要寻找任一个元素的右边或则左边第一个比自己大或则小的元素的位置,此时我们就要想到可以用单调栈了。
- 使用单边栈主要有三个判断条件
当前元素小于栈顶元素
当前元素等于栈顶元素
当前元素大于栈顶元素
2.编程题
2.1 739. 每日温度
请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] ans = new int[n];
Deque<Integer> stack = new LinkedList<>();//维护一个递减栈,栈中保存数组下标
for(int i=0;i<n;++i){
while(!stack.isEmpty() && temperatures[i]>temperatures[stack.peek()]){
ans[stack.peek()] = i - stack.pop();
}
stack.push(i);
}
return ans;
}
}
2.2 496. 下一个更大元素 I
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1和nums2中所有整数 互不相同
nums1 中的所有整数同样出现在 nums2 中
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int n = nums1.length;
int[] ans = new int[n];
Arrays.fill(ans,-1);
Deque<Integer> stack = new LinkedList<>();//维护递减栈,存储数组下标
HashMap<Integer,Integer> map = new HashMap<>();
for(int i=0;i<n;++i){
map.put(nums1[i],i);
}
for(int i=0;i<nums2.length;++i){
while(!stack.isEmpty() && nums2[i]>nums2[stack.peek()]){
if(map.containsKey(nums2[stack.peek()])){
int index = map.get(nums2[stack.peek()]);
ans[index] = nums2[i];
}
stack.pop();
}
stack.push(i);
}
return ans;
}
}
2.3 503. 下一个更大元素 II
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
输入数组的长度不会超过 10000。
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
Arrays.fill(ans,-1);
Deque<Integer> stack = new LinkedList<>();//维护一个递减栈,保存数组下标
for(int i=0;i<2*n;++i){
while(!stack.isEmpty() && nums[i%n]>nums[stack.peek()]){
ans[stack.pop()] = nums[i%n];
}
stack.push(i%n);
}
return ans;
}
}
2.4 42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
//双指针
class Solution {
public int trap(int[] height) {
int ans = 0;
for(int i=0;i<height.length;++i){
//第一个柱子和最后一个柱子不接雨水
if(i == 0 || i == height.length-1) continue;
int lHeight = height[i];//记录左边柱子的最高高度
int rHeight = height[i];//记录右边边柱子的最高高度
for(int l=i-1;l>=0;--l){
lHeight = Math.max(lHeight,height[l]);
}
for(int r=i+1;r<height.length;++r){
rHeight = Math.max(rHeight,height[r]);
}
int h = Math.min(lHeight,rHeight)-height[i];
if(h > 0) ans += h;
}
return ans;
}
}
//双指针优化
//如果height[left] < height[right] 则leftMax一定小于rightMax
//同理height[left] >= height[right],则leftMax一定大于等于rightMax
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
//动态规划
class Solution {
public int trap(int[] height) {
/**
1.ldp[i]表示height[i]柱子左边柱子的最大高度
rdp[i]表示height[i]柱子右边边柱子的最大高度
2.ldp[i] = Math.max(height[i],ldp[i-1]);
rdp[i] = Math.max(height[i],rdp[i+1]);
3.ldp[0] = height[0];rdp[n-1] = height[n-1];
4.求左边顺序,求右边逆序
*/
int n = height.length;
if(n<=2) return 0;
int[] ldp = new int[n];
int[] rdp = new int[n];
//记录每个柱子左边柱子的最大高度
ldp[0] = height[0];
for(int i = 1; i < n; ++i){
ldp[i] = Math.max(height[i],ldp[i-1]);
}
//记录每个柱子右边柱子的最大高度
rdp[n-1] = height[n-1];
for(int i = n-2; i >= 0; --i){
rdp[i] = Math.max(height[i],rdp[i+1]);
}
//求和
int ans= 0;
for(int i=0;i<n;++i){
int h = Math.min(ldp[i],rdp[i])-height[i];
if(h > 0) ans += h;
}
return ans;
}
}
//单调栈
class Solution {
public int trap(int[] height) {
Deque<Integer> stack = new LinkedList<>();//维护递减栈,保存数组下标
int ans = 0;
for(int i = 0; i < height.length; ++i){
while(!stack.isEmpty() && height[i] > height[stack.peek()]){
int mid = stack.pop();//中间柱子的下标
if(!stack.isEmpty()){
//height[stack.peek()] 相对于中间柱子的左边柱子高度
//height[i] 相对于中间柱子的右边柱子高度
//height[mid] 中间柱子高度
int h = Math.min(height[stack.peek()],height[i])-height[mid];
int w = i - stack.peek() -1;
ans += h*w;
}
}
stack.push(i);
}
return ans;
}
}
2.5 84. 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
1 <= heights.length <=105
0 <= heights[i] <= 104
//本题是要找每个柱子左右两边第一个小于该柱子的柱子
class Solution {
public int largestRectangleArea(int[] heights) {
int[] temp = new int[heights.length+2];
System.arraycopy(heights,0,temp,1,heights.length);
Deque<Integer> stack = new LinkedList<>();
int ans = 0;
for(int i = 0; i < temp.length; ++i){
while(!stack.isEmpty() && temp[i] < temp[stack.peek()]){
int mid = stack.pop();
int h = temp[mid];
int w = i-stack.peek()-1;
ans = Math.max(ans,w*h);
}
stack.push(i);
}
return ans;
}
}