题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]
。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
给定代码
class Solution {
public int largestRectangleArea(int[] heights) {
}
}
题解一
暴力破解。遍历一遍数组,遍历到下标 i i i 时,分别左右延申到一个 h e i g h t < i height<i height<i 的下标。然后计算面积值。
class Solution {
public int largestRectangleArea(int[] heights) {
int max = 0;
for (int i = 0 ; i < heights.length ; i++)
{
int left = i, right = i;
while(--left >= 0)
{
if (heights[left] < heights[i])
break;
}
while(++right < heights.length)
{
if (heights[right] < heights[i])
break;
}
max = Math.max(max, heights[i] * (right - left - 1));
}
return max;
}
}
时间复杂度: O ( n 2 ) O(n^2) O(n2)。循环内部需要分别向左、向右延申,延申过程复杂度 O ( n ) O(n) O(n)。
空间复杂度: O ( 1 ) O(1) O(1)。
题解二
题解一的问题在于,遍历到每个下标时,要分别往左往右延申。
所以使用单调栈来记录一个递增的高度值下标。(栈保存的是下标!)
每当元素进栈时,应该把栈内比它大的元素依次弹出。这样就能保证栈内的顺序是递增的。
例如,[2,1,5,6,2,3]
(下面的进栈操作都是这些值对应的下标进栈)
i = 0:栈为空,2进栈。
i = 1:栈顶的2大于1,2出栈,1进栈。
i = 2:栈顶的1小于5,5直接进栈。
i = 3:栈顶的5小于6,6直接进栈。
i = 4:栈顶的6大于2,6出栈,同理,5也出栈,此时栈顶1小于2,2可以进栈。
i = 5:栈顶的2小于3,3进栈。
i = 6:循环结束。
这样就能保证栈内的顺序是递增的。
然后在元素出栈的时候,就能确定它的左右两边应该延申到哪(宽度)。
在上面的i = 4
时,6 出栈,右边 2 代表是 6 右边第一个比 6 小的元素,所以右边延申到i
即可。当 6 出栈时,5 成为新的栈顶,那么 5 就是 6 左边第一个比 6 小的元素,所以左边延申到栈的新顶部就行。
目前的做法会造成遍历一次数组后,栈还没有清空,还需要一个循环来清空。
优化方法就是使用哨兵元素,在数组的头和尾分别插入一个高度为0的哨兵元素,形成一个新数组。
使得栈底始终有一个高度为 0 的元素(数组为正整数,不可能比 0 小)。当遍历到新数组尾部时(高度为 0 ),因为栈为单调增特性,所有的栈元素都会出栈,只留下起始烧饼和终止哨兵,两个的高度都为0.
class Solution {
public int largestRectangleArea(int[] heights) {
int max = 0;
Deque<Integer> stack = new ArrayDeque<>();
int [] newHeight = new int[heights.length + 2];
System.arraycopy(heights, 0, newHeight, 1, heights.length);
// 首先将哨兵头扔进栈,由于值为0,所以永远不会出栈,当个老赖
stack.offerFirst(0);
for (int i = 1 ; i < newHeight.length ; i++)
{
// 栈顶值大于当前值,循环弹出元素
while (newHeight[stack.peekFirst()] > newHeight[i])
{
// 弹出元素的过程中,计算这个元素左右延申的最大面积
int tmp = stack.pollFirst();
max = Math.max(max, (i - stack.peekFirst() - 1) * newHeight[tmp]);
}
// 上面的操作确保栈中没有元素比这个更大,放心入栈
stack.offerFirst(i);
}
return max;
}
}
时间复杂度: O ( n ) O(n) O(n)。每个元素进栈 n n n次,出栈 n n n次。
空间复杂度: O ( n ) O(n) O(n)。栈内元素最多 n n n个,还开了一个 n + 2 n + 2 n+2的数组。
通过这题可以知道,求一个数组中每一个元素左右两侧出现的第一个小于该元素值的下标,可以用单调栈的方式以 O ( n ) O(n) O(n)的时间复杂度求解。
思考一下:
求当前数组中,每个元素左右两侧出现的第一个大于该元素值的下标?
这里使用单调递减栈即可在 O ( n ) O(n) O(n)复杂度内得出结果。