zoukankan      html  css  js  c++  java
  • 单调栈问题汇总

    单调栈(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的元素

    下面就来看一下单调栈的应用。

    42. 接雨水

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

    img

    上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

    分析:本题是单调栈的典型应用。首先,我们考虑该选取单调递减栈还是单调递增栈?

    由于要接到雨水,显然,必须要形成“凹”的形状,由此可以确定,应当是选取递减栈——按递减的序列把高度存进去(递减指的是从栈底至栈顶递减),一旦发现当前的高度大于栈顶元素了,说明形成了凹槽。但是计算凹槽的面积,除了高度,还需要知道宽度,因此,我们应该在栈中存放下标而非直接存放高度。

    class Solution {    
        public int trap(int[] height) {
            int n = height.length;
            int total = 0; // 能接到的雨水总量
            Stack<Integer> s = new Stack<>(); // 存放数组下标,而非数组元素
            int i = 0;
            while(i < n) {
                // 维护一个单调递减栈
                if(s.isEmpty() || height[i] < height[s.peek()]) {
                    s.push(i);
                    i++;
                }else {
                    int bottom = s.pop();
                    if(s.isEmpty()) continue; // 关键
                    int w = i - s.peek() - 1;
                    int h = Math.min(height[s.peek()], height[i]) - height[bottom];
                    total += w * h;
                }
            }
            return total;
        }
    }
    

    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;
        }
    }
    
  • 相关阅读:
    MacOS Mojave启动U盘制作及安装方法详细介绍
    Boostnote for Mac——如何使用TeX精美地编写数学公式
    pycharm pro 2019 mac如何重构?
    Understand教程—使用搜索功能的几种方法
    Understand Mac项目视图初级使用
    文本代码编辑CudaText for Mac使用方法
    java学习笔记之面向对象接口
    java学习笔记之遍历ArrayList的三种方法
    java学习笔记之面向对象static,final关键字
    java学习笔记之eclipse下jar包导入
  • 原文地址:https://www.cnblogs.com/kkbill/p/13287780.html
Copyright © 2011-2022 走看看