一起刷LeetCode——柱状图中最大的矩形(单调栈)

138 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。

分析

  • 勾勒出的矩形,面积为长*宽,求最大面积
  • 由于使用直角坐标系的习惯,在柱状图中画矩形,一般是会以x轴为主,然后确定y轴高度,于是得到了第一种方法,枚举宽度

方法一:确定宽度

  • 双重循环,一重循环左边界值,在确定了左边界的基础上,再循环右边界的值,即可得到宽,从边界中获取最小高度作为矩形的高,计算后得到最大面积,但时间复杂度为O(n^2)

方法二:确定高度

  • 换个角度看直角坐标系,确定y,寻找x,即在确定高度
  • 遍历数组,以当前遍历到的高度为高,宽则是需要从当前高度的位置向左找到最左边不大于当前高度的位置,向右找到的最右边不大于当前高度的位置算出宽,计算后可得到最大面积

优化

  • 方法1没办法优化,双重循环是不能少的。不过可以优化方法2中关于宽的计算
  • 如果数组下标i<j,但height[i]>=height[j],在找j的左边界的时候,在i,j之后的下标m,对应高度为height[m]的矩形的左边界一定不会是i。所以如果要找到最大的矩形面积,可以维护一个下标构成的数组,这个数组严格单调递增,对应的高度也是单调递增。但是这样在计算最大面积的时候会有冗余的次数,比如heights是[2,2,2],得到的这个数组是[0,1,2],对应的高度是[2,2,2],其中下标为1的结果是冗余的计算,不会影响最终的结果,因此继续优化
  • 因此当遍历到不影响最终结果的位置时,不用放到维护的下标数组里,当遍历到的heights[n]小于下标数组中最大的高度时,可以确定当前高度heights[n]下,左边界不是下标数组的最后一项,可能是更前面的,此处比较容易想到使用栈来管理。当找到第一个小于height[n]的下标时,可以认为是左边界。
  • 基于上面是记录小于height[n]的坐标来作为左边界,再加上数组是有边界的,因此maxLen = heights.length - 1,在确定某个高度,获取宽度的时候,边界其实是(左边界,右边界],直接相减就可以得到宽度。为了数组下标0的情况,0的左边界为-1,因此,在维护栈的时候,首先要在栈中放入一个-1。

代码

/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    heights.push(0)
    let len = heights.length
    let stack = []
    let arr_index = []
    let max = 0
    stack.push(0)
    arr_index.push(-1)
    for(let i=0;i<len;i++) {
        let s = stack[stack.length - 1]
        if(heights[i] >= s) {
            stack.push(heights[i])
            arr_index.push(i)
        } 
        if(heights[i] < s){
            while(stack.length > 0){
                let n = stack[stack.length - 1]
                let index = arr_index[arr_index.length - 1]
                if(heights[i] >= n ) {
                    stack.push(heights[i])
                    arr_index.push(i)
                    break
                } else {
                    stack.pop()
                    arr_index.pop()
                    if(stack.length > 0) {
                        let t = arr_index[arr_index.length-1]
                        max = Math.max(max, (i-t - 1) * n)
                    }
                    
                }
            }
        }
    }
    return max
};

总结

  • 单调栈如果想不清楚的话,可以通过画图的方式来帮助理解
  • 如果题目中给出了一个数组,并且有比较明显的单调性的倾向,解题时需要维护题目给出的数组中符合某种条件的项,并且有暴力解决的迷惑性,在暴力之后超时的题目,可以尝试使用单调栈来解决问题
  • 今天也是有收获的一天