Skip to main content

柱状图中最大的矩形

题目

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

提示:
  • 1 <= heights.length <=10⁵
  • 0 <= heights[i] <= 10⁴
示例
输入: heights = [2, 1, 5, 6, 2, 3]
输出: 10
解释: 最大的矩形为图中红色区域, 面积为 10

84-largest-rectangle-area

输入: heights = [2, 4]
输出: 4
解释: 最大的矩形为图中红色区域, 面积为 4

84-largest-rectangle-area

题解

不管是暴力解法还是单调栈. 思路都是根据某个 height, 比如示例一中 heights[2] 这个柱子, 也就是 5, 然后往两边扩散, 找到离这个柱子最近的比它小的柱子:

  • 往左扩散: heights[1]heights[2] 小, 说明要想维持 heights[2] 这个高度, 左边的阈值就得是索引 1
  • 往右扩散: heights[4]heights[2] 小, 说明要想维持 heights[2] 这个高度, 右边的阈值就得是索引 4

这样一来, 维持高度为 heights[2] 的最大宽度就是 4 - 1 - 1 = 2.

84-largest-rectangle-area

对于暴力解法, 需要遍历每个高度, 然后以此为节点, 再往左往右遍历, 找到左右边界的阈值. 但这会导致 O(n²) 的时间复杂度.

因此可以通过空间换时间的策略, 使用单调栈, 来预先找到每个节点左右边界的阈值. 具体原理看注释.

/**
* @param {number[]} heights
* @return {number}
*/
var largestRectangleArea = function (heights) {
const n = heights.length

// 从右往左, 找到比当前元素小的左边界
// 需要理解为什么默认值为 -1, 比如一个元素左边所有元素都比它大,
// 这就导致从当前高度往左, 一定是可以拉满当前高度的, 那么左边界就是 -1
//
// PS: 一开始没想明白为什么是 -1, 而不是 0, 因为存储的是索引, 所以左边界值就要是 -1
const left = new Array(n).fill(-1)

// 从左往右, 找到比当前元素小的右边界
// 需要理解为什么默认值为 n, 比如一个元素右边所有元素都比它大,
// 这就导致从当前高度往右, 一定是可以拉满当前高度的, 那么右边界就是 n
const right = new Array(n).fill(n)

const stack = []

// 标准的单调栈框架, 找到比当前元素小的右边界
for (let i = 0; i < n; i++) {
while (stack.length > 0 && heights[stack[stack.length - 1]] > heights[i]) {
right[stack.pop()] = i
}

stack.push(i)
}

// 标准的单调栈框架, 找到比当前元素小的左边界
for (let i = n - 1; i >= 0; i--) {
while (stack.length > 0 && heights[stack[stack.length - 1]] > heights[i]) {
left[stack.pop()] = i
}

stack.push(i)
}

let max = 0

// 根据左右边界和当前柱子的高度, 来找到最大值
for (let i = 0; i < n; i++) {
const height = heights[i]
const leftIdx = left[i]
const rightIdx = right[i]
max = Math.max(max, (rightIdx - leftIdx - 1) * height)
}

return max
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)