力扣-84. 柱状图中最大的矩形
1.题目介绍
题目地址(84. 柱状图中最大的矩形 - 力扣(LeetCode))
https://leetcode.cn/problems/largest-rectangle-in-histogram/
题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4] 输出: 4
提示:
1 <= heights.length <=105
0 <= heights[i] <= 104
2.题解
2.1 暴力枚举(枚举左边界-底边)
思路
这里我们枚举左边界和右边界的范围,这样我们也就确定了heights数组的遍历范围,从中根据木桶效应找到最小的高,面积=底边*高
代码
- 语言支持:C++
C++ Code:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
int ans = 0;
// 枚举左边界
for(int left = 0; left < n; left++){
int h = heights[left];
for(int right = left; right < n; right++){
h = min(h, heights[right]);
ans = max(ans, (right - left + 1) * h);
}
}
return ans;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n^2)\)
- 空间复杂度:\(O(n)\)
2.2 暴力枚举(枚举高)
思路
我们首先笃定矩形的高为某个值,然后以这个高为中心,向两边进行中心扩展,知道遇到小于这个高的情况(这时候再加上这个小于的就与我们笃定的假设相驳)
这里需要注意一下,如果我们使用的是while循环,最后结束的情况是第一个不满足条件的left或right而不是最后一个满足条件的left或right,
所以这里两种处理方法,要不然后面使用left+1和right-1, 结果改为(right - 1 - left - 1 + 1) * height; 要不然while判断条件改为如下代码所示即可
代码
- 语言支持:C++
C++ Code:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
int ans = 0;
// 枚举高
for(int mid = 0; mid < n; mid++){
int left = mid, right = mid;
while(left-1 >= 0 && heights[left-1] >= heights[mid]){
left--;
}
while(right+1 < n && heights[right+1] >= heights[mid]){
right++;
}
ans = max(ans, (right - left + 1) * heights[mid]);
}
return ans;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n^2)\)
- 空间复杂度:\(O(n)\)
2.3 单调栈
思路
这里我们继续2.2中确定高的思路,从中间向两边扩展,我们要找到左边第一个小于该高的索引和右边第一个小于该高的索引
如果向2.2一样,我们每次先遍历到每个高,然后向四周进行扩展,这样最坏情况相当于我们每次都需要遍历一次整个heights数组
有没有办法我们事先知道每个高它左侧第一个更小高和右侧第一个更小高呢? 这里我们就思考使用单调栈来解决问题.
我们对于该heights数组进行事先处理.求出更小高并存储到两个数组中
首先对于左侧元素,我们从左侧开始遍历,这里有一个性质,如果出现一个高hi小于其左侧的hx,那么对于索引i右侧的所有元素,他们找更小高,只有可能找到hi或者更左侧的高,而不会找到hx,
因为 x < i < ..., 如果 hi < h... 那么就是hi; 如果 hi > h, 又因为 hi < hx, 所以 hx > hi > h..., 必然不可能是hx
我们考虑用一个单调栈来维护这些高(帮助我们快速找到更小高),
1.如果当前高小于等于栈顶高,说明从此处以后,该栈顶高不可能作为更小高存在,我们不需要进行判断,将其从栈中弹出即可;
2.如果当前高大于栈顶高,说明我们找到了最近更小高(由于栈先进后出的性质,栈顶的是我们最近压进去的),将其进行存储
3.将当前高进行存储,压入栈(整体栈还是单调栈)
注意一点,这里我们找到了最近更小高的索引,但是我们实际需要的不是最近更小高的索引(第一个不满足条件的高), 而是要的最后一个满足条件的高(边界)
即实际上是left + 1 和 right - 1, 但这里我们存的是 left 和 right, 所以最后(right - 1 - (left + 1) + 1) = (right - left - 1)为实际的底边
但是我们也可以在存储动动手脚, 改为'left[i] = stk.empty()? 0:stk.top() + 1;' 和 right[j] = stk.empty()? n - 1 :stk.top() - 1;
找到的就是边界了!
代码
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
stack<int> stk;
vector<int> left(n), right(n);
for(int i = 0; i < n; i++){
int h = heights[i];
while(!stk.empty() && h <= heights[stk.top()]){
stk.pop();
}
left[i] = stk.empty()?-1:stk.top();
stk.push(i);
}
stk = stack<int>();
for(int j = n - 1; j >= 0; j--){
int h = heights[j];
while(!stk.empty() && h <= heights[stk.top()]){
stk.pop();
}
right[j] = stk.empty()?n:stk.top();
stk.push(j);
}
int ans = 0;
for(int i = 0; i < n; i++){
ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
};