LeetCode/柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
在遍历过程中,左边的很多信息对右边仍然有用处,不是单纯的求一个左侧最大高度或者最大面积
所以无法用动态规划来求解,而且对于每一个待考虑的矩阵,对其扩张的时候,有用的信息是其邻近的信息
而不是左右边缘的信息,同样也无法用双指针求解,先考虑暴力求解,再进行优化
1. 暴力求解
暴力求解的思路,计算所有矩形的面积,具体遍历每个位作为左边界,再往右探索该左边界的每个矩阵
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
int ans = 0;
// 枚举左边界
for (int left = 0; left < n; ++left) {
int minHeight = INT_MAX;//以i为左边界的矩形只能乘以遍历过的最小高度
// 枚举右边界
for (int right = left; right < n; ++right) {
// 确定高度
minHeight = min(minHeight, heights[right]);
// 计算面积
ans = max(ans, (right - left + 1) * minHeight);
}
}
return ans;
}
};
左右扩张
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
int ans = 0;
for (int mid = 0; mid < n; ++mid) {
// 枚举高
int height = heights[mid];
int left = mid, right = mid;
// 确定左右边界
while (left - 1 >= 0 && heights[left - 1] >= height) {
--left;
}
while (right + 1 < n && heights[right + 1] >= height) {
++right;
}
// 计算面积
ans = max(ans, (right - left + 1) * height);
}
return ans;
}
};
2. 单调栈
对于左右扩张的方法,本质上的问题是一维数组中对每一个数的两侧找到第一个比自己小的元素
以左侧为例,从前往后遍历的时候,如果数是降序,那每个数的左边界都是降序序列最左端,如果是升序,那每个数的左边界是左侧邻近数
这就意味着我们还要用到遍历过的信息,而且是以一种相反方向的访问方式,使用栈能很好地满足这种操作的需求
把遍历过的数放到栈里面,遍历的时候将数与栈头对比,如果是降序,即该数小于栈头,就一直出栈到该数的左边界(第一个小于它的值)
把它变成升序情况,如果是升序,左边界就是栈头,同时把数入栈,因为该信息后面还要用到
右侧遍历也是相同的原理
框架如下
stack<int> mono_stack;//单调栈
for (int i = 0; i < n; ++i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) //注意考虑栈空情况,降(等)序出栈
mono_stack.pop();//先出栈再赋值,因为对于降序序列一直要出栈到位升序为止才能找到左边界
left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
mono_stack.push(i);//比较和赋值完再入栈
}
单调栈
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> left(n), right(n);//记录每个柱子左右,满足条件的最远下标
//条件指得是第一个小于柱子高度的位置
stack<int> mono_stack;//单调栈
for (int i = 0; i < n; ++i) {//从左往右遍历找每个元素左侧满足条件最远位置
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
mono_stack.pop();//如果左边这个元素大于当下节点,那他是包容右边的
//提供了当下节点构成左边矩形区域一部分
//所以可以不用看了,直接去掉,因为它被当下节点截断了,不会再影响后面元素
//同时要露出我们的左边界
}
//如果满足增序关系,那左边界就是他前一个元素
left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
//当下节点的左边界,即是左边第一个小于它的元素,因为大的元素全部去掉了
mono_stack.push(i);//当下节点入栈,给后面元素继续判断
}
mono_stack = stack<int>();//单调栈
for (int i = n - 1; i >= 0; --i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
mono_stack.pop();
}
right[i] = (mono_stack.empty() ? n : mono_stack.top());
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
};