单调栈

介绍

单调栈是什么?顾名思义,单调栈中的元素具有单调递增性或单调递减性,即栈中元素按顺序从小到大或者从大到小排列。从小到大单调递增称为单调递增栈,从大到小单调递减称为单调递减栈。

把数组的元素依次压入栈中,如果是单调递增栈,压入元素currentData每次和栈顶元素peekData进行比较,如果currentData < peekData, 为了保证栈的单调递增性,栈顶元素须弹出,然后currentData再不断和新的栈顶元素比较,直到currentData > peekData或者栈为空。

如果单调递减栈,则条件反之,满足条件current > peekData,栈顶元素弹出。

例子[1,5,6,3,4],模拟单调递增栈:

  • 开始压入1,5,6,符合栈的单调递增性,栈内元素为{1,5,6}。
  • 压入3,比栈顶元素6小,则6弹出,栈内元素为{1,5}。
  • 3又比栈顶元素5小,则5弹出,栈内元素为{1}。
  • 3比栈顶元素1大,则压入栈,栈内元素为{1,3}。
  • 4比栈顶元素3大,则压入栈,栈内元素为{1,3,4}。

单调递增栈代码如下所示,单调递减栈,修改比较条件即可。

void monotonicStack() {
    Stack<Integer> stack = new Stack();
    int[] nums = {1,5,6,3,4};
    for(int i = 0; i < nums.length; i++) {
        while(!stack.isEmpty() && nums[i] < stack.peek()) {
            stack.pop();
        }

        stack.push(nums[i]);
    }
}

应用

单调栈可以用于确定元素的左右边界,找到数组中每个元素左右两边第一个比它大或者比它小的值。下面看leetcode的题目分析,单调栈如何应用的。

[84]、柱状图中最大的矩形

题目

题目链接

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。
image.png

思路解析

遍历所有柱子,计算每个柱子能占据的面积,比较确定占据的最大面积。
如果相邻柱子比当前柱子高即可以连成一片计算,如上面的题目示例,当前柱子高度为5,右边的柱子高度为6,则连成一片5*2=10。

所以先要找到每根柱子的左右边界,因为左右边界的范围内的柱子都比当前柱子高,以当前柱子高度作为矩形的高,左右边界相差的距离为矩形的宽,两者相乘即为当前柱子能占据的最大面积。按照此方法依次遍历所有柱子,计算出所有柱子占据的最大面积,最后比较得出最大值。

左边界和右边界即为比当前柱子高且距离最远的柱子,换个思路就是找到左右两边第一个比当前柱子小的,其相邻位置就是左右边界。

如下图所示,确定hegihts[3]的左右边界的方法,就是找到hegihts[1]hegihts[6]这两个离hegihts[3]最近且小于它的柱子。确定左边界为hegihts[2]右边界为hhegihts[5],从而计算出hegihts[3]能占据矩形面积。

image.png

下面代码使用单调递增栈,用于确定左右边界,如果当前压入栈的柱子a高度小于栈顶的柱子b,说明当前压入的柱子a是右边第一个小于栈顶b的柱子,根据单调递增性,在栈顶下面的柱子c就是左边第一个小于栈顶b的柱子,两者的相邻位置即为栈顶柱子c的左右边界。

image.png

但是还有两种特殊情况

  1. 栈为空没有左边界。
  2. 遍历完柱子,栈内还有元素。

可以使用哨兵元素解决这两种特殊情况,在左和右添加高度为0的柱子作为哨兵,左边哨兵保证左边界的存在,右边哨兵由于为0是最小值,可以保证栈内元素全部出栈。

代码如下所示:

public static int largestRectangleArea(int[] heights) {
    int[] copyHeights = new int[heights.length + 2];
    System.arraycopy(heights, 0, copyHeights, 1, heights.length);
    Deque<Integer> stack = new ArrayDeque<>(heights.length+2);
    int maxArea = 0;
    for (int i = 0; i < copyHeights.length; i++) {
        while (!stack.isEmpty() && copyHeights[i] < copyHeights[stack.peek()]) {
            int h = copyHeights[stack.pop()];
            int left = stack.peek()+1;
            int right = i -1;
            maxArea = Math.max((right - left + 1) * copyHeights[i], maxArea);
        }
        stack.push(i);
    }
    return maxArea;
}

[739]、每日温度

题目

题目链接

请根据每日气温列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

思路解析

遍历数组中的每个温度,在右边找到第一个比当前温度值高的温度,然后两者索引相减即为等待天数。

使用单调递减栈,当前栈顶温度值如果小于于压入栈的温度值,当前压入栈的温度值即为第一个大于栈顶的温度值,把压入栈的索引-栈顶的索引=等待天数,然后栈顶温度出栈。

代码如下:

public int[] dailyTemperatures(int[] temperatures) {
    ArrayDeque<Integer> stack = new ArrayDeque<>(temperatures.length);
    int[] results = new int[temperatures.length];
    for (int i = 0; i < temperatures.length; i++) {
        while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
            int current = stack.pop();
            results[current] = i - current;
        }
        stack.push(i);
    }
    return results;
}

其他常见题目

42. 接雨水
496. 下一个更大元素
901. 股票价格跨度

多学多练就可以掌握单调栈的解题套路。

总结

  1. 单调栈是栈顶元素按照单调递增性或递减性的特殊栈。

  2. 单调递增栈确定数组中每个元素的左右两边第一个比它小的值,单调递减栈确定数组中每个元素的左右两边第一个比它大的值。

  3. 设当前压入元素为a,栈顶元素为b, 栈顶元素的下一个元素为c。如果a>b,是栈顶元素弹出,则a是右边第一个大于栈顶的元素,c为左边第一个大于栈顶的元素。

    image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值