单调栈与单调队列

单调栈与单调队列

基本功
Stack<Integer> indexStack = new Stack<Integer>();
        indexStack.push(0);
        
        for(int i=1;i<=height.length;i++){
        	/*当前尚未存入递减栈的边界*/
            int currentH = (i < height.length ? height[i] : 0);
            while(!indexStack.empty() && height[indexStack.peek()] < currentH){
                //触发后处理
                }
                
            }
            indexStack.push(i);
        }
单调栈与单调队列
  • 单调栈,是用栈的方式将元素维持一定单调性(从栈底到栈顶单调递增或单调递减)的数据结构。
  • 单调队列,是用队列的方式将元素维持一定单调性(从队头到队尾单调递增或单调递减)的数据结构。
  • 单调双端队列,双端队列的方式将元素维持一定单调性(从队头到队尾单调递增或单调递减)的数据结构。
    使用此数据结构,问题的解决可以做到线性时间复杂度。
单调栈的实用性质(用于解决“凹”“凸”型问题)

单调递增栈

  • 发现从左起第一个比当前数字小的元素。(解决“凸”型问题

单调递减栈

  • 发现从左起第一个比当前数字大的元素。(解决“凹”型问题

若符合栈的单调性,则其中存放的元素是连续的

相关练习题目:
题目讲解
  • 42. Trapping Rain Water
    Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
    在这里插入图片描述
    The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

Example:
Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6


这个是典型的“凹”型问题,我们要维护一个单调递减栈,这样就可以转化为“凹”型问题。我们将边界从左到右遍历存入递减栈中,一旦发现当前数字大于栈顶元素了,就会出现一个凹槽,此时便可以收集雨水。当前的尚未存入递减栈中的元素是右边界,需要依次从递减栈中取出两个元素,第一个取出的是最低点,第二个取出的是左边界。根据木桶原理,能装多少水取决于最短的那个边界。minH = Math.min(Left,Right); 再用这个高度减去最低点,乘以左右边界的间距就得到装水量了。(单调栈问题一般不直接存储数组值的大小,而是数组的元素对应的索引)。

public int trap(int[] height) {
        int sum=0;
        Stack<Integer> indexStack = new Stack<Integer>();
        indexStack.push(0);
        
        for(int i=1;i<=height.length;i++){
        	/*当前尚未存入递减栈的边界*/
            int currentH = (i < height.length ? height[i] : 0);
            while(!indexStack.empty() && height[indexStack.peek()] < currentH){
                int h1=height[indexStack.peek()];//得到最低点
                indexStack.pop();//使用完,可以将最低点弹出
                if(!indexStack.empty()){
                	/*此时左边界不用弹出,因为它有可能作为最低点与栈中的前一个元素,再次组成凹槽*/
                    int h2=height[indexStack.peek()];
                    int indexLeft = indexStack.peek();//左边界的索引
                    int minH = Math.min(h2,currentH);//找出较低的挡板
                    sum += (minH-h1)*(i-indexLeft-1);
                }
                
            }
            indexStack.push(i);
        }
        return sum;
    }

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.
在这里插入图片描述
Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].
在这里插入图片描述
The largest rectangle is shown in the shaded area, which has area = 10 unit.
Example:
Input: [2,1,5,6,2,3]
Output: 10


这道题是“凸”型问题,我们需要维护一个递增栈,若当前矩形的高度小于栈顶元素时,会触发计算,就会计算栈里所有的矩形的最大矩形面积。因为这个递增栈里只会存入连续增长的矩形,例如[1,5,6];若当前矩形高度是2时,就会开始计算栈内存入矩形的最大面积。

// 单调栈
    public int largestRectangleArea(int[] heights) {            
        int sum=0;
        Stack<Integer> indexStack = new Stack<Integer>();
        indexStack.push(0);
        
        for(int i=1;i<=heights.length;i++){
            int myh = (i<heights.length ? heights[i] : 0);//在数组的末尾增加一个0,确保所有元素能被弹出处理
            while(!indexStack.empty() && heights[indexStack.peek()] > myh){
                int h=heights[indexStack.peek()];//获取弹出元素的高度
                indexStack.pop();//将元素弹出
                int preIndex = indexStack.empty()==false ? indexStack.peek() : -1;// 若栈内还有元素,则前一个下标是栈顶元素,若栈为空,则下标为-1
                sum = Math.max(sum,h*(i-preIndex-1));//i-preIndex-1为宽度
            }
            indexStack.push(i);
        }
        return sum;
    }

在单调栈中,每个元素只进栈并处理一次,解决问题的关键弹出处理。若当前的元素破坏了单调性,就会触发处理栈顶元素的操作,而当前的触发元素有时候是解决问题的一部分,比如 42. Trapping Rain Water 中作为右边界。有时候仅仅起触发作用,比如84. Largest Rectangle in Histogram,如果仅起触发作用,可能还需要在数组的末尾增加一个专门用于触发的元素,确保最后栈中的元素都能弹出处理。


496. Next Greater Element I
You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subset of nums2. Find all the next greater numbers for nums1’s elements in the corresponding places of nums2.

The Next Greater Number of a number x in nums1 is the first greater number to its right in nums2. If it does not exist, output -1 for this number.

Example 1:
Input: nums1 = [4,1,2], nums2 = [1,3,4,2].
Output: [-1,3,-1]
Explanation:
For number 4 in the first array, you cannot find the next greater number for it in the second array, so output -1.
For number 1 in the first array, the next greater number for it in the second array is 3.
For number 2 in the first array, there is no next greater number for it in the second array, so output -1.


503. Next Greater Element II
Given a circular array (the next element of the last element is the first element of the array), print the Next Greater Number for every element. The Next Greater Number of a number x is the first greater number to its traversing-order next in the array, which means you could search circularly to find its next greater number. If it doesn’t exist, output -1 for this number.

Example 1:
Input: [1,2,1]
Output: [2,-1,2]
Explanation: The first 1’s next greater number is 2;
The number 2 can’t find next greater number;
The second 1’s next greater number needs to search circularly, which is also 2.


这两题若要用单调栈来解决,思路是一样的。找到下一个比自己大的元素属于“凹”型问题,维护一个递减栈即可解决。
代码:

 public int[] nextGreaterElements(int[] nums) {
        int[] res = new int[nums.length];
        Arrays.fill(res,-1);
        Stack<Integer> stack = new Stack<Integer>();
        stack.push(0);
        for(int i=1;i<2*nums.length;i++){//需要乘以2,才能够循环遍历
            int j=i%nums.length;
            while(!stack.empty() && nums[stack.peek()] < nums[j]){//nums[j] 为触发元素,在递减栈中即为一部分元素的下一个比自己大的元素
                res[stack.pop()%nums.length]=nums[j];
            }
            stack.push(j);
        }
        return res;
    }

739. Daily Temperatures
Given a list of daily temperatures T, return a list such that, for each day in the input, tells you how many days you would have to wait until a warmer temperature. If there is no future day for which this is possible, put 0 instead.

For example, given the list of temperatures T = [73, 74, 75, 71, 69, 72, 76, 73], your output should be [1, 1, 4, 2, 1, 1, 0, 0].

Note: The length of temperatures will be in the range [1, 30000]. Each temperature will be an integer in the range [30, 100].

这个问题其实可以转化为从自己这个元素需要走多少步才能走到下一个比自己大的元素那
代码:

public int[] dailyTemperatures(int[] T) {
        int[] res = new int[T.length];
        Stack<Integer> stack = new Stack<Integer>();
        stack.push(0);
        for(int i=1;i<T.length;i++){
            while(!stack.empty() && T[stack.peek()] < T[i]){
                res[stack.peek()]=i - stack.peek();//下标作差即为需要走的距离
                stack.pop();
            }
            stack.push(i);
        }
        return res;
    }

239. Sliding Window Maximum
Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window.

Example:

Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
Output: [3,3,5,5,6,7]
Explanation:

Window position Max


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

此题需要维护一个双端单调队列,这是一个“凹”型问题,我们需要维护一个双端单调递减队列,为什么会需要一个双端队列,而不是一个栈来实现,是因为存在窗口失效元素此时要将这些窗口失效元素从队头清理出去,而加入,处理操作仍然和单调栈一样。
代码:

//双端单调队列方法
    public int[] maxSlidingWindow(int[] nums, int k) {
        int resLength = nums.length - k+1;
        int[] res=new int[resLength];
        if(nums.length==0 || k==0) return new int[0];
        
        Deque<Integer> deque = new ArrayDeque<Integer>();
        int j=0;
        for(int i=0;i<nums.length;i++){
            while(deque.size()>0 && deque.getFirst() <= i-k){//清除窗口过期元素
                deque.removeFirst();
            }
            
            while(deque.size()>0 && nums[deque.peekLast()] < nums[i]){//维护双端单调递减队列
                deque.pollLast();
            }
            deque.offer(i);
            
            if(i+1 >= k) res[j++]=nums[deque.getFirst()];//第一个窗口的元素完全录入后,产生的结果才是有效的
        }
        return res;
    }

参考来源:
https://www.cnblogs.com/grandyang/p/8887985.html
https://blog.csdn.net/qq_17550379/article/details/86519771

LeetCode Monotone Stack Summary 单调栈小结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值