代码随想录-单调栈(Java)

每日温度

链接: 739. 每日温度

思路

单调栈适用问题:

通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置

单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。

就是用一个栈来记录我们遍历过的元素

在使用单调栈的时候首先要明确如下几点:

  1. 单调栈里存放的元素是什么?

单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

  1. 单调栈里元素是递增呢? 还是递减呢?

注意以下讲解中,顺序的描述为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定比较懵。

这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。

即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。

使用单调栈主要有三个判断条件。

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

T[i] <= T[st.top()] 符合递增,直接push进去

T[i] > T[st.top()]元素弹出进行处理

因为本题不止弹出一次,大于也可能连续大于栈中元素,所以用while循环

代码

class Solution {
  // 版本 1
    // 思路清晰版本
    public int[] dailyTemperatures(int[] temperatures) {

        int lens=temperatures.length;
        int []res=new int[lens];

        /**
        如果当前遍历的元素 大于栈顶元素,表示 栈顶元素的 右边的最大的元素就是 当前遍历的元素,
        	所以弹出 栈顶元素,并记录
        	如果栈不空的话,还要考虑新的栈顶与当前元素的大小关系
        否则的话,可以直接入栈。
        注意,单调栈里 加入的元素是 下标。
        */
        Deque<Integer> stack=new LinkedList<>();
        stack.push(0);
        for(int i=1;i<lens;i++){

            if(temperatures[i]<=temperatures[stack.peek()]){
                stack.push(i);
            }else{
                while(!stack.isEmpty()&&temperatures[i]>temperatures[stack.peek()]){
                    res[stack.peek()]=i-stack.peek();
                    stack.pop();
                }
                stack.push(i);
            }
        }

        return  res;
    }
    // 版本二
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int[] results = new int[temperatures.length];
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        for (int i = 1;i < temperatures.length;i++){
            while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
                results[stack.peek()] = i - stack.peek();
                stack.pop(); 
            }
            stack.push(i);
        }
        return results;
    }
}

下一个更大元素

链接: 496.下一个更大元素 I

思路

思路和传统单调栈思路很像,稍加变式

nums2进行单调栈的处理,而在处理单调栈符合条件的情况时,我们看nums2的元素在不在nums1中,在的话,我们将该元素赋值给results数组。

看起来绕,实现起来不绕。

代码

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        int[] results = new int[nums1.length];
        Arrays.fill(results,-1);
        HashMap<Integer,Integer> map = new HashMap<>();
        for (int i = 0;i < nums1.length;i++){
            map.put(nums1[i],i);
        }
        Stack<Integer> st = new Stack<>();
        st.push(0);
        for (int i = 0;i < nums2.length;i++){
            while (!st.isEmpty() && nums2[i] > nums2[st.peek()]){
                if (map.containsKey(nums2[st.peek()])){
                    int index = map.get(nums2[st.peek()]);
                    results[index] = nums2[i];
                }
                st.pop();
            }
            st.push(i);
        }
        return results;
    }
}

下一个更大元素ll

链接: 503.下一个更大元素II

思路

循环数组,设成二倍的形式,在处理时使用取余操作

i % size (是一种自己未建立起来的思想)

其余同单调栈。

代码

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int[] results = new int[nums.length];
        Arrays.fill(results,-1);
        Stack<Integer> st = new Stack<>();
        int size = nums.length;
        st.push(0);
        for (int i = 1;i < 2 * size;i++){
            while (!st.isEmpty() && nums[i % size] > nums[st.peek()]){
                int pre = st.pop();
                results[pre] = nums[i % size]; 
            }
            st.push(i % size);
        }
        return results;
    }
}

接雨水

链接: 42. 接雨水

思路

本题比较复杂,在理清思路之后还会因为很多小细节没注意到而犯错

最好是写完代码按照自己代码的思路在脑海中遍历一遍(自己还未能做到。静下心来。)

接雨水这道题目也需要用递增的单调栈来解决,思路有些许不同

本题中,我们需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积。

所以我们在遇到height[i] > height[st.peek()]的情况时,除了要弹出栈顶元素(mid)以外,我们还要记录下弹出后的栈顶元素(不可弹出)left,而right = i;我们比较左右的高度,Math.min(height[right],height[left]) 木桶原理,选择最矮的一边减去中间的高度,于是这就是我们的高

宽是i - left - 1;

之所以不把left弹出,是因为我们把每个弹出的元素作为mid,也就是作为我们面积的底,弹出后就少了很多情况(也会报错)

注意:遇到相同高度的柱子怎么办

else if (height[i] == height[st.peek()]){
                st.pop();
                st.push(i);
            }

反正宽是用下标相减得到的。

代码

class Solution {
    public int trap(int[] height) {
        int sum = 0;
        if (height.length <= 2) return sum;
        Stack<Integer> st = new Stack<>();
        st.push(0);
        for (int i = 1;i < height.length;i++){
            // int stTop = st.peek();
            if (height[i] < height[st.peek()]){
                st.push(i);
            }else if (height[i] == height[st.peek()]){
                st.pop();
                st.push(i);
            }else{
                while (!st.isEmpty() && height[i] > height[st.peek()]){
                int mid = st.pop();
                if (!st.isEmpty()){
                    int right = i;
                    int left = st.peek();
                    int h = Math.min(height[right],height[left]) - height[mid];
                    int w = right - left - 1;
                    int hold = h * w;
                    if (hold > 0)   sum += hold;
                    // stTop = st.peek();
                    }
                }
                st.push(i);
            }
        }
        return sum;
    }
}

柱状图中最大的矩形

链接: 84.柱状图中最大的矩形

思路

本题大体思路和接雨水很像,但也有很大区别

本题抓住一句话:找每个柱子左右两边第一个小于该柱子的柱子。
在这里插入图片描述
找到小于的,说明找到了边界,就可以求中间那段的面积

本题h = height[mid] 其中mid = st.peek();

自己与接雨水的问题混淆了。

代码

class Solution {
    public int largestRectangleArea(int[] heights) {
        int result = 0;
        Stack<Integer> st = new Stack<>();
        int[] newHeights = new int[heights.length + 2];
        newHeights[0] = 0;
        newHeights[newHeights.length - 1] = 0;
        for (int i = 1;i < newHeights.length - 1;i++){
            newHeights[i] = heights[i - 1];
        }
        heights = newHeights;
        st.push(0);
        for (int i = 1;i < heights.length;i++){
            if (heights[i] > heights[st.peek()]){
                st.push(i);
            }else if (heights[i] == heights[st.peek()]){
                st.pop();
                st.push(i);
            }else {
                int sum = 0;
                while (!st.isEmpty() && heights[i] < heights[st.peek()]){
                    int mid = st.pop();
                    int left = st.peek();
                    // int h = Math.min(heights[i],heights[left]);
                    int h = heights[mid];
                    int w = i - left - 1;
                    int hold = h * w;
                    // if (hold > 0) sum += hold;
                    result = Math.max(result,hold);
                }
                st.push(i);
                // result = Math.max(result,sum);
            }
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值