单调栈问题汇总

Posted kkbill

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调栈问题汇总相关的知识,希望对你有一定的参考价值。

单调栈(Monotone Stack)

栈的应用中有一类问题称为单调栈(Monotone Stack)问题,可以巧妙的将某些问题的时间复杂度降到「O(n)级别」。那么什么是单调栈呢?

所谓单调栈,就是保持栈中的元素是单调的。假设把数组 [2 1 4 6 5]依次入栈,并保持栈的单调递增性,如下:

  1. 元素2入栈,此时栈中元素为[2]
  2. 元素1入栈,由于此时1小于栈顶元素2,把1入栈的话就不满足单调递增性了,于是先把栈顶元素2弹出,再让元素1入栈,此时栈中元素为[1]
  3. 元素4入栈,由于此时4大于栈顶元素1,可以满足递增性,故入栈,此时栈中元素为[1,4]
  4. 元素6入栈,同上,此时栈中元素为[1,4,6]
  5. 元素5入栈,同第2步,在入栈前先把栈顶元素6弹出,故此时栈中元素为[1,4,5]

由于栈中元素(从栈底至栈顶)保持单调递增性,因此,有这样一个性质:

假设当前元素为a,栈顶元素(若栈非空)就是元素a左侧第一个小于a的元素

同样的,如果维护一个单调递减栈,那么就有:

假设当前元素为a,栈顶元素(若栈非空)就是元素a左侧第一个大于a的元素

84. 柱状图中最大的矩形

方法1:暴力法O(n^2)

算法思路:
从最基础的思路出发,已知矩形面积的计算公式为:高度 × 宽度。就本题而言,每个小矩形的高度是确定,我们可以固定一个小矩形i,以heights[i]为高度,以位置 i 为中心向左右两侧扩散,使得扩展到的柱子的高度均不小于 h,直到到达边界、或者不能再向外延伸了。
换句话说,我们需要找到左右两侧最近的高度小于 h 的柱子,这样这两根柱子之间(不包括其本身)的所有柱子高度均不小于 h,并且就是 i 能够扩展到的最远范围。

这种做法的时间复杂度是O(n^2),因为遍历数组需要O(n),固定位置i向两侧延伸时最大也需要O(n),即两层for循环。

class Solution {
    /*
    暴力解法
    时间复杂度:O(n^2)
    空间复杂度:O(1)
    */
    public int largestRectangleArea(int[] heights) {
        int maxArea = 0;
        for(int i = 0; i < heights.length; i++) {
            int h = heights[i];
            int left = i, right = i;
            while(left >= 0 && heights[left] >= h) left--;
            while(right < heights.length && heights[right] >= h) right++;
            int w = right - left - 1;
            maxArea = Math.max(maxArea, w * h);
        }
        return maxArea;
    }
}

方法2:单调栈O(n)

class Solution {
    public int largestRectangleArea(int[] heights) {
        // 预处理:添加哨兵
        int n = heights.length;
        int[] temp = new int[n + 1];
        for(int i = 0; i < n; i++) {
            temp[i] = heights[i];
        }
        temp[n] = 0;// 哨兵
        heights = temp;
        // 正式处理
        Stack<Integer> s = new Stack<>();
        int maxArea = 0, i = 0;
        while(i < heights.length) {
            if(s.isEmpty() || heights[i] >= heights[s.peek()]) {
                s.push(i);
                i++;
            }else{
                int t = s.pop();
                if(s.isEmpty()) {
                    maxArea = Math.max(maxArea, i * heights[t]);
                }else {
                    maxArea = Math.max(maxArea, (i - s.peek() - 1) * heights[t]);
                }
            }
        }
        return maxArea;
    }
}

单调栈:不使用Stack,使用Deque

使用双端队列来模拟栈,速度更快一些。因为在Java中,Stack其实不推荐使用的。

class Solution {
    public int largestRectangleArea(int[] heights) {
        // 预处理:添加哨兵
        int n = heights.length;
        int[] temp = new int[n + 1];
        for(int i = 0; i < n; i++) {
            temp[i] = heights[i];
        }
        temp[n] = 0;// 哨兵
        heights = temp;
        // 正式处理
        Deque<Integer> s = new LinkedList<>();
        int maxArea = 0, i = 0;
        while(i < heights.length) {
            if(s.isEmpty() || heights[i] >= heights[s.peekLast()]) {
                s.add(i);
                i++;
            }else{
                int t = s.pollLast();
                if(s.isEmpty()) {
                    maxArea = Math.max(maxArea, i * heights[t]);
                }else {
                    maxArea = Math.max(maxArea, (i - s.peekLast() - 1) * heights[t]);
                }
            }
        }
        return maxArea;
    }
}

方法3:暴力优化(本题最优解)

class Solution {
    public int largestRectangleArea(int[] heights) {
        if(heights.length == 0) return 0;

        int maxArea = 0;
        int n = heights.length;
        /*
        left[i]  表示位置i左侧第一个小于heights[i]的位置
        right[i] 表示位置i右侧第一个小于heights[i]的位置
        */
        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = -1;
        for(int i = 1; i < n; i++) {
            int k = i-1;
            while(k >= 0 && heights[k] >= heights[i]) {
                // k--;
                k = left[k];
            }
            left[i] = k;
        }

        right[n-1] = n;
        for(int i = n-2; i >= 0; i--) {
            int k = i+1;
            while(k < n && heights[k] >= heights[i]) {
                // k++;
                k = right[k];
            }
            right[i] = k;
        }

        /*
        计算面积
        对于高度为heights[i]的柱子,以其为中心可以形成的最大矩形面积等于
        heights[i] × (right[i] - left[i] - 1)
        */
        for(int i = 0; i < n; i++) {
            int currArea = heights[i] * (right[i] - left[i] - 1);
            maxArea = Math.max(maxArea, currArea);
        }
        return maxArea;
    }
}


以上是关于单调栈问题汇总的主要内容,如果未能解决你的问题,请参考以下文章

OI省选算法汇总

[转]OI省选算法汇总

golang 代码实现单调栈

单调栈

线性表--单调栈

线性表--单调栈