单调栈总结

今天做周赛又碰到了单调栈的题目,之前没有做好总结,这次好好总结下

1.基本思想

单调栈求解的基本问题

在一个线性数据结构中,为任意一个元素找左边和右边第一个比自己大/小的位置,要求O(n)的复杂度

基本解法很容易想到O(n^2)的解法,关键是O(n)的解法,就需要借助单调栈了。单调栈的一大优势就是线性的时间复杂度,所有的元素只会进栈一次,而且一旦出栈后就不会再进来了。

单调栈的性质

  1. 单调栈里的元素具有单调性:单调递减栈=>向栈生长的地方单调递减;单调递增栈=>向栈生长的地方单调递增

  2. 元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除

  3. 使用单调栈可以找到元素向左遍历第一个比他小的元素,也可以找到元素向左遍历第一个比他大的元素。

一般解题思路

  1. 栈中不存放值,而是存放下标;
  2. 看是需要找左边(右边)第一个小于某数的数,还是大于某数的数

总结

递减栈会剔除波谷,留下波峰;递增栈剔除波峰,留下波谷

2.例题

lc 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.

思路:

对于每一个洼地,想要找到左起第一个比它小的数,则可以使用单调递减栈
对于每个数,和栈顶元素比较,如果小于栈顶元素,则入栈;如果大于栈顶元素了,
则入栈会破坏栈顶规则,那么就该出栈比较了,
因为构成水洼至少需要两个点,那么现在栈顶的点是最低的挖槽,再下一个栈顶才是左边界,当前的点是右边界
取左右两边的最低高度,减去挖槽的高度,再乘以左右边界的宽度,就得到了这个挖槽的蓄水体积

代码:

class Solution {
    public int trap(int[] height) {
        int res = 0;
        Stack<Integer> stack = new Stack<>();
        int i = 0;
        while (i < height.length) {
            if (stack.isEmpty() || height[i] < height[stack.peek()]) {
                stack.push(i++);
            } else {
                int bottom = stack.peek();
                stack.pop();
                if (stack.isEmpty()) continue;
                int leftBound = stack.peek();
                int rain = (Math.min(height[leftBound], height[i]) - height[bottom]) * (i - leftBound - 1);
                res += rain;
            }
        }
        return res;
    } 
}

lc 84. Largest Rectangle in Histogram

题意:

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.

思路:

单调递增栈
应该先处理高板子,再处理低板子,最好高板子的左边都是比它低的,那么可以维护一个单调递增栈,
然后每次遇到height[i] < stack.peek() 的话,说明该来计算面结了,那么就可以每次pop出栈顶元素,
当前的矩形面积是栈顶元素的高度与栈顶元素与i的宽度构成的;
(因为栈顶元素的下面的元素都比它小,也就是之前pop出的都比它大,那么当前到i之间的高度最低的就是栈顶元素的高度)

代码:

class Solution {
    public int largestRectangleArea(int[] heights) {
        List<Integer> nums = new ArrayList<>();
        for (int h : heights) nums.add(h);
        nums.add(0);
        Stack<Integer> stack = new Stack<>();
        int i = 0, res = 0;
        while (i < nums.size()) {
            if (stack.isEmpty() || nums.get(i) > nums.get(stack.peek())) {
                stack.push(i++);
            } else {
                int topIndex = stack.peek();
                stack.pop();
                res = Math.max(res, nums.get(topIndex) * (stack.isEmpty() ? i : i - stack.peek() - 1));
            }
        }
        return res;
    }
}

lc 962. Maximum Width Ramp

题意:

Given an array A of integers, a ramp is a tuple (i, j) for which i < j and A[
i] <= A[j]. The width of such a ramp is j - i.
Find the maximum width of a ramp in A. If one doesn't exist, return 0.

思路:

找到左边第一个比它小的数
那么用递增栈还是递减栈呢?=>
如果我们从右向左遍历每个数的话,希望找到左起第一个比该数小的数,也就是希望栈顶是第一个比这个数小的数,=>
那么如果我们能保证栈顶右边的数,都比栈顶的数大,就是对于右边的数来说,栈顶的数就是第一个小于它的数了 =>
可以维护一个递减栈

代码:

class Solution {
    public int maxWidthRamp(int[] A) {
        Stack<Integer> stack = new Stack<>();
        int n = A.length;
        for (int i = 0; i < n; i++) {
            if (stack.isEmpty() || A[stack.peek()] > A[i]) {
                stack.push(i);
            }
        }
        int res = 0;
        for (int i = n - 1; i > 0; i--) {
            while (!stack.isEmpty() && A[i] >= A[stack.peek()]) {
                res = Math.max(res, i - stack.pop());
            }
        }
        return res;
    }
}

总结

能用单调栈的题目一般会有多种阶解法,比如双指针、hash map等,但是复杂度可能达不到O(n);有时候单调栈比较难想,先考虑下其他解法;
单调栈的题,也有一定的套路,有时候单调递增递减栈不好确定,可以顺着题意想一想哪种能解;

原文地址:https://www.cnblogs.com/shawshawwan/p/10166459.html