LeetCode 84 Largest Rectangle in Histogram (Tag:Array Difficulty:Hard)

137 阅读2分钟

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

前言

关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!

题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

示例 1:

image.png
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:

image.png
输入: heights = [2,4]
输出: 4

链接:leetcode-cn.com/problems/la…

题解

  1. 暴力解法。想法是每次以 heights[i] 为高,向左右扩展矩形,直到遇到比 heights[i] 小的值停止扩展,那么这个矩形的面积就为 heights[i] * 宽度。具体代码如下,这个方法时间复杂度为 O(n^2),会超时。
/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    const n = heights.length
    let max = -1
    for (let i = 0; i < n; i++) {
        let left = right = i
        let height = heights[i]
        
        while (left - 1 >= 0 && heights[left - 1] >= height) {
            --left;
        }
        
        while (right + 1 < n && heights[right + 1] >= height) {
            ++right;
        }
        
        max = Math.max(max, (right - left + 1) * height);
    }
    return max
};
  1. 单调栈。上面的方法每次一个高度,都需要遍历左右来找边界。我们可以利用单调栈的性质来记录边界。单调栈顾名思义,就是单调递增/递减的栈,满足栈里的元素是单调增/减的。这里我们利用单调递增栈,当一个元素入栈时,可以判断栈顶元素和自己的大小,如果比自己小,那么,说明以自己为高的矩形左边界就是栈顶元素的下标,自己入栈;如果比自己大,就把栈顶元素 pop 出去,直到找到比自己小的元素,记录比自己小的元素的下表,作为边界。

这样,我们通过从左到右构造单调栈可以记录左边界,从右到左构造单调栈记录右边界,最后计算矩形的面积。具体代码如下,时间复杂度 O(n)

/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    const n = heights.length
    let left = new Array(n)
    let right = new Array(n)
    let stack = []
    
    for (let i = 0; i < n; i++) {
        while(stack.length && heights[stack[stack.length - 1]] >= heights[i]) {
            stack.pop()
        }
        left[i] = stack.length === 0 ? -1 : stack[stack.length - 1]
        stack.push(i)
    }
    
    stack = []
    for (let i = n - 1; i >= 0; i--) {
        while(stack.length && heights[stack[stack.length - 1]] >= heights[i]) {
            stack.pop()
        }
        right[i] = stack.length === 0 ? n : stack[stack.length - 1]
        stack.push(i)
    }
        
    let ans = 0;
    for (let i = 0; i < n; ++i) {
        ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
    }
    return ans;
};
  1. 单调栈优化。很容易注意到,在我们从左到右构造递归栈时,其实右边界也同时确定了,我们不需要再从右到左遍历一遍数组来记录右边界。

需要注意的是right数组元素需要默认为 n。

代码如下

/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    const n = heights.length
    let left = new Array(n)
    let right = new Array(n).fill(n)
    let stack = []
    
    for (let i = 0; i < n; i++) {
        while(stack.length && heights[stack[stack.length - 1]] >= heights[i]) {
            right[stack[stack.length - 1]] = i
            stack.pop()
        }
        left[i] = stack.length === 0 ? -1 : stack[stack.length - 1]
        stack.push(i)
    }
   
    let ans = 0;
    for (let i = 0; i < n; ++i) {
        ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
    }
    return ans;
};