矩形问题
一般使用动态规划,单调栈。
例题
柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
思路一
暴力解法,定义两个指针i,j,两个区间直接的面积就是:min(i…j) * (j-i+1),遍历两个指针的组合得到最大的面积数,这种解法的时间复杂度是On^2
代码
class Solution {
public int largestRectangleArea(int[] heights) {
int result = 0;
for (int i = 0; i < heights.length; i++) {
int min = heights[i];
for (int j = i; j < heights.length; j++) {
min = Math.min(min, heights[j]);
result = Math.max(result, (j - i + 1) * min);
}
}
return result;
}
}
思路二
中心扩展法。
思路三
使用单调栈,单调栈适用于什么场景?找最值,单调栈:栈顶是最大的元素,单调队列:队头是最大的元素
说白了,这题考的基础模型其实就是:在一维数组中对每一个数找到第一个比自己小的元素。这类“在一维数组中找第一个满足某种条件的数”的场景就是典型的单调栈应用场景。
具体思路
暴力方法是直接计算,会有很多重复计算,同时时间复杂度较高。所以我们可以用空间换取时间的做法,降低时间复杂度。
对于每一个height[i],我们可以向左和向右对外扩展,直到找到比它小的数才中断扩展。这个时候就是包含height【i】的最大矩形面积。
新增定义两个数组left,right,left[i]分别表示:
- 对于元素num[i],left[i]表示在num[i]左边的比num[i]小的最近的元素
- 对于元素num[i],right[i]表示在nums[i]右边的比num[i]小的最近的元素。
那么计算公式就是:
height[i] * (right[i] -left[i])
考虑边界情况:
如果左边的数都比height[i]大,left[i]=-1,
如果右边的数都比height[i]大,right=height.length
那么如何求left[i]和right[i]呢
如果直接向左或向右遍历,那么复杂度依然为On^2,对于这种每个元素按照某个顺序遍历都必须插入到栈,而且插入元素后栈顶一直保持是最大的数的场景,我们可以使用单调栈。单调栈:从栈底到栈顶,是单调递增的。而且,元素插入的顺序都是有意义的。
时间复杂度分析
单调栈的时间复杂度是多少?直接计算十分困难,但是我们可以发现:
每一个位置只会入栈一次(在枚举到它时),并且最多出栈一次。
因此当我们从左向右/总右向左遍历数组时,对栈的操作的次数就为 O(N)O(N)。所以单调栈的总时间复杂度为 O(N)
代码
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
int[] right = new int[n];
Deque<Integer> mono_stack = new ArrayDeque<Integer>();
for (int i = 0; i < n; ++i) {
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
mono_stack.pop();
}
left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek())