Leetcode刷题 -- 单调栈

单调栈

顾名思义,首先是一个后入先出的栈,其次,栈内存储的数据是单调有序的。

  • 单调递增栈:单调递增栈就是从栈底到栈顶数据是从小到大
  • 单调递减栈:单调递减栈就是从栈底到栈顶数据是从大到小

注意,上述所说的单调增或者单调减,并不是说栈内存的元素是单调的,比如说我们对一个数组nums进行遍历,那么栈内存的值为index,而真正单调的是nums[index]

伪代码

Stack<int> stack

for(遍历数组){
    while(栈不为空 && 栈顶元素小于当前比较元素){
        相应处理
        出栈
    }
    当前入栈
}
----遍历完成得到的就是一个单调递减的栈(栈底到栈顶依次减小)


1.单调栈里的元素具有单调性,栈中元素只能是单调递增或者单调递减
2.元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除;
3.使用单调栈可以找到元素向左遍历第一个比它小的元素,也可以找到元素向左遍历第一个比它大的元素

总结:

        单调栈通常用在一维数组上,如果遇到的问题和前后元素之间的大小有关系的话,可以考虑使用单调栈来解决。下面会给出几个例题,关键是想清楚,如果使用单调栈,每个元素出栈时,代表什么意义?。

例题

​​​​​​503. 下一个更大元素 II

题目:

        给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

解题思路:

        使用单调栈,但是单调栈中存的数据应该是数组的下标,因此栈内单调的其实是num[index]

代码

public static int[] nextGreaterElements1(int[] nums) {
    // 定义一个和原数组长度相同的数组
    // 这个数组用来存储nums数组对应位置下一个更大的元素值
    int[] resNums = new int[nums.length];
    
    // 定义一个栈,即要使用到的单调栈,本次是单调递减栈
    Stack<Integer> stack = new Stack<>();

    // 遍历数组
    for (int i = 0; i < nums.length; i++) {
        
        //当栈不为空,并且, 栈顶坐标的元素小于当前对比的元素时,
        // 说明这个元素为在nums[] 中,比栈顶坐标大的第一个数
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            // 在resNums中存入这个值 --- 题目要求返回的是更大的数,因此存入数组元素
            resNums[stack.peek()] = nums[i];
            // resNums已经存入了元素,因此可以pop掉
            stack.pop();
        }
        // 不满足while中的条件,则需要入栈
        // 注意入栈是要存入当前元素对应的坐标
        stack.push(i);
    }

    // 因为是循环数组,因此还要遍历一轮,找到比后入栈的元素更大的元素
    // 找到比对应位置大的元素就还执行和之前一样的操作
    // 这次不需要入栈操作
    for (int i = 0; i < nums.length; i++) {
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            resNums[stack.peek()] = nums[i];
            stack.pop();
        }
    }
    
    // 此时,栈就是一个单调栈,因为没有比自己打的元素,因此可以直接pop,对应位置赋值为-1
    while (!stack.isEmpty()) {
        resNums[stack.peek()] = -1;
        stack.pop();
    }
    return resNums;
}

此题中,栈内存的是数组的坐标

出栈的意义:对比的数据就是比栈顶元素坐标大的第一个数字。

​​​​​​84. 柱状图中最大的矩形

public int largestRectangleArea(int[] heights) {
    // 创建一个新的数组,两端为0
    // 目的是,遍历原数组第一个数的时候,可以保证能够正常入栈
    // 遍历到原数组最后一个数的时候,可以保证能够正常出栈计算面积
    int len = heights.length + 2;
    int[] newHeights = new int[len];
    newHeights[0] = 0;
    newHeights[len - 1] = 0;
    System.arraycopy(heights, 0, newHeights, 1, heights.length);
    
    // 定义左边界,用于计算面积;定义result,用于存储计算面积中,最大的值
    int left;
    int result = 0;
    // 定义单调栈
    Stack<Integer> stack = new Stack<>();
    // 遍历新数组
    for (int i = 0; i < len; i++) {
        // 单调递增栈。栈不为空,并且栈顶元素大于即将入栈元素 所要执行的逻辑
        while (!stack.isEmpty() && newHeights[stack.peek()] > newHeights[i]) {
            // 以当前栈顶元素为高,计算可以拓展的面积
            // 因为右侧的数据比当前值小,因此只能往左拓展了 ----可以认为,右侧最多只能取到i
            int height = newHeights[stack.peek()];
            // 把当前栈顶元素弹出
            stack.pop();
            // 弹出后,当前栈顶元素就是左边界
            left = stack.peek();
            // 右边界其实就是i,也就是即将入栈的数据 ----注意计算面积的时候,i-left-1
            result = Math.max(result, (i - left - 1) * height);
        }
        // 比当前元素大的都已经弹出去了,当前元素继续入栈
        stack.push(i);
    }
    return result;
}



42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

    public static int trap(int[] height) {
        // 维护一个单调递减栈,如果栈内存在两个元素,那么栈顶的元素是小于栈顶下一个元素的
        // 遍历数组,如果遇到的元素比当前栈顶的元素大,那么这种情况就可以接雨水了
        // 接雨水的量其实就是求矩形的面积
        // 宽度就是 (当前遍历数组坐标 - 栈顶元素下一个元素的坐标)
        // 高度就是 (当前遍历数组坐标的值 与 栈顶元素下一个元素的坐标的值)较小的一个,减去当前栈顶元素坐标值

        Stack<Integer> stack = new Stack<>();
        int result = 0;
        for (int i = 0; i < height.length; i++) {
            while (!stack.isEmpty() && height[stack.peek()] < height[i]) {
                int rightHeight = height[i];
                int popHeight = height[stack.pop()];
                if (stack.isEmpty()) {
                    break;
                }
                int left = stack.peek();
                int leftHeight = height[left];
                result += (Math.min(rightHeight, leftHeight) - popHeight) * (i - left - 1);
            }

            stack.push(i);
        }
        return result;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值