题目
标题和出处
标题:柱状图中最大的矩形
难度
7 级
题目描述
要求
给定整数数组 heights \texttt{heights} heights 表示柱状图中各个柱子的高度,其中每个柱子的宽度为 1 \texttt{1} 1,返回在该柱状图中的矩形的最大面积。
示例
示例 1:
输入:
heights
=
[2,1,5,6,2,3]
\texttt{heights = [2,1,5,6,2,3]}
heights = [2,1,5,6,2,3]
输出:
10
\texttt{10}
10
解释:上图为一个柱状图,其中每个柱子的宽度为
1
\texttt{1}
1。最大的矩形为图中红色区域,面积为
10
\texttt{10}
10。
示例 2:
输入:
heights
=
[2,4]
\texttt{heights = [2,4]}
heights = [2,4]
输出:
4
\texttt{4}
4
数据范围
- 1 ≤ heights.length ≤ 10 5 \texttt{1} \le \texttt{heights.length} \le \texttt{10}^\texttt{5} 1≤heights.length≤105
- 0 ≤ heights[i] ≤ 10 4 \texttt{0} \le \texttt{heights[i]} \le \texttt{10}^\texttt{4} 0≤heights[i]≤104
解法
思路和算法
为了得到柱状图中最大的矩形,对于下标 i i i,需要找到最大的下标 j j j 和最小的下标 k k k,满足 j < i < k j < i < k j<i<k 且 heights [ j ] < heights [ i ] \textit{heights}[j] < \textit{heights}[i] heights[j]<heights[i] 和 heights [ k ] < heights [ i ] \textit{heights}[k] < \textit{heights}[i] heights[k]<heights[i],则存在一个宽度为 k − j − 1 k - j - 1 k−j−1、高度为 heights [ i ] \textit{heights}[i] heights[i] 的矩形,计算该矩形的面积并更新矩形的最大面积。
直观的做法是遍历柱状图中的每个柱子,对于每个下标 i i i,向两边遍历寻找对应的下标 j j j 和 k k k,得到以 heights [ i ] \textit{heights}[i] heights[i] 为高的最大矩形宽度并计算矩形的面积。假设数组 heights \textit{heights} heights 的长度是 n n n,该做法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超出时间限制,因此需要优化。
创建两个长度为 n n n 的数组 left \textit{left} left 和 right \textit{right} right,对于每个下标 i i i, left [ i ] \textit{left}[i] left[i] 和 right [ i ] \textit{right}[i] right[i] 分别记录对应的下标 j j j 和 k k k。初始时, left \textit{left} left 的全部元素为 − 1 -1 −1, right \textit{right} right 的全部元素为 n n n。这道题中可以假设 heights [ − 1 ] = heights [ n ] = − ∞ \textit{heights}[-1] = \textit{heights}[n] = -\infty heights[−1]=heights[n]=−∞。在遍历数组 heights \textit{heights} heights 的过程中填入 left \textit{left} left 和 right \textit{right} right 的值。
为了得到 left \textit{left} left 和 right \textit{right} right 的值,可以使用单调栈,单调栈存储数组 heights \textit{heights} heights 的下标,满足从栈底到栈顶的下标对应的元素单调递增。
从左到右遍历数组 heights \textit{heights} heights,当遍历到下标 i i i 时,进行如下操作:
-
如果栈不为空且栈顶下标对应的元素大于等于 heights [ i ] \textit{heights}[i] heights[i],则将栈顶下标出栈,并将栈顶下标对应的 right \textit{right} right 值设为 i i i,重复该操作直到栈为空或者栈顶下标对应的元素小于 heights [ i ] \textit{heights}[i] heights[i];
-
如果栈不为空,则栈顶下标对应的元素小于 heights [ i ] \textit{heights}[i] heights[i],因此将 i i i 对应的 left \textit{left} left 值设为栈顶下标;
-
将 i i i 入栈。
上述操作中,下标 i i i 入栈的条件是栈为空或者栈顶下标对应的元素小于 heights [ i ] \textit{heights}[i] heights[i],因此可以保证从栈底到栈顶的下标对应的元素单调递增。
遍历结束之后,对于每个下标 i i i,令 j = left [ i ] j = \textit{left}[i] j=left[i], k = right [ i ] k = \textit{right}[i] k=right[i],则有 heights [ j ] < heights [ i ] \textit{heights}[j] < \textit{heights}[i] heights[j]<heights[i], heights [ k ] ≤ heights [ i ] \textit{heights}[k] \le \textit{heights}[i] heights[k]≤heights[i]。如果 heights [ k ] < heights [ i ] \textit{heights}[k] < \textit{heights}[i] heights[k]<heights[i],则高度为 heights [ i ] \textit{heights}[i] heights[i] 的最大矩形宽度即为 k − j − 1 k - j - 1 k−j−1。如果 heights [ k ] = heights [ i ] \textit{heights}[k] = \textit{heights}[i] heights[k]=heights[i],则高度为 heights [ i ] \textit{heights}[i] heights[i] 的最大矩形宽度大于 k − j − 1 k - j - 1 k−j−1,但是在遍历到下标 k k k 时可以得到高度为 heights [ k ] \textit{heights}[k] heights[k] 的矩形宽度为 right [ k ] − left [ k ] − 1 \textit{right}[k] - \textit{left}[k] - 1 right[k]−left[k]−1,该宽度大于 k − j − 1 k - j - 1 k−j−1。由于一定存在一个柱子,该柱子右边没有和该柱子高度相同的柱子,因此在遍历到该柱子时即可得到以该柱子的高度为矩形高度的矩形最大宽度,并计算得到矩形的最大面积。
对于下标 i i i,高度为 heights [ i ] \textit{heights}[i] heights[i] 的最大矩形的宽度为 right [ i ] − left [ i ] − 1 \textit{right}[i] - \textit{left}[i] - 1 right[i]−left[i]−1(根据上述分析,可能存在 k > i k > i k>i 满足 heights [ k ] = heights [ i ] \textit{heights}[k] = \textit{heights}[i] heights[k]=heights[i],但是这里仍然认为该宽度是最大矩形的宽度),面积为 ( right [ i ] − left [ i ] − 1 ) × heights [ i ] (\textit{right}[i] - \textit{left}[i] - 1) \times \textit{heights}[i] (right[i]−left[i]−1)×heights[i]。遍历 0 ≤ i < n 0 \le i < n 0≤i<n,即可得到柱状图中的矩形的最大面积。
考虑一个例子: heights = [ 2 , 1 , 4 , 4 ] \textit{heights} = [2, 1, 4, 4] heights=[2,1,4,4]。对应的 left = [ − 1 , − 1 , 1 , 1 ] \textit{left} = [-1, -1, 1, 1] left=[−1,−1,1,1], right = [ 1 , 4 , 3 , 4 ] \textit{right} = [1, 4, 3, 4] right=[1,4,3,4],矩形的最大面积是 8 8 8。
对于下标 0 0 0,最大矩形的宽度为 1 − ( − 1 ) − 1 = 1 1 - (-1) - 1 = 1 1−(−1)−1=1,高度为 2 2 2,面积为 2 2 2。
对于下标 1 1 1,最大矩形的宽度为 4 − ( − 1 ) − 1 = 4 4 - (-1) - 1 = 4 4−(−1)−1=4,高度为 1 1 1,面积为 4 4 4。
对于下标 2 2 2,最大矩形的宽度为 3 − 1 − 1 = 1 3 - 1 - 1 = 1 3−1−1=1,高度为 4 4 4,面积为 4 4 4。注意 heights [ 2 ] = heights [ 3 ] \textit{heights}[2] = \textit{heights}[3] heights[2]=heights[3],因此最大矩形的宽度应该为 2 2 2,但是在下标 2 2 2 处计算的最大矩形的宽度为 1 1 1,在下标 3 3 3 处将会计算到最大矩形的宽度为 2 2 2。
对于下标 3 3 3,最大矩形的宽度为 4 − 1 − 1 = 2 4 - 1 - 1 = 2 4−1−1=2,高度为 4 4 4,面积为 8 8 8。
代码
class Solution {
public int largestRectangleArea(int[] heights) {
int length = heights.length;
int[] left = new int[length];
int[] right = new int[length];
Arrays.fill(left, -1);
Arrays.fill(right, length);
Deque<Integer> stack = new ArrayDeque<Integer>();
for (int i = 0; i < length; i++) {
int height = heights[i];
while (!stack.isEmpty() && heights[stack.peek()] >= height) {
right[stack.pop()] = i;
}
if (!stack.isEmpty()) {
left[i] = stack.peek();
}
stack.push(i);
}
int area = 0;
for (int i = 0; i < length; i++) {
area = Math.max(area, (right[i] - left[i] - 1) * heights[i]);
}
return area;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 heights \textit{heights} heights 的长度。需要遍历数组 heights \textit{heights} heights 一次,每个下标最多入栈和出栈各一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 heights \textit{heights} heights 的长度。空间复杂度主要取决于两个数组 left \textit{left} left 和 right \textit{right} right 以及栈空间,两个数组的长度都是 n n n,栈内元素个数不会超过 n n n。