柱状图中最大的矩形
暴力解法:
不管怎么勾勒矩形,最大面积的矩形中肯定有一个柱子被完全包括在其中。这样我们就可以遍历每一个柱子,以该柱子高度为基准勾勒矩形,求所有矩形的最大值。具体做法就是寻找当前柱子左侧第一个更低的柱子和右侧第一个更低的柱子,记录两者的下标计算宽度,高度就是当前柱子的高度,这样就能计算出矩形的面积。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int sum = 0;
for(int i = 0; i < heights.size(); i++) {
int left = i;
int right = i;
for(; left >= 0; left--) {
if(heights[left] < heights[i]) break;
}
for(; right < heights.size(); right++) {
if(heights[right] < heights[i]) break;
}
int w = right - left - 1;
sum = max(sum, w * heights[i]);
}
return sum;
}
};
可以看出,这道题是接雨水那道题的镜像版,接雨水是寻找当前柱子左右最高的柱子,记录高度;而这道题是寻找左右第一个更低的柱子,同时记录下标。
双指针优化:
仍然是事先计算每个柱子左右第一个更低柱子的下标,维护两个数组,利用之前的结果避免重复计算。但由于这道题是要维护第一个比当前元素小的下标,具体实现要比接雨水复杂。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int sum = 0;
vector<int> leftLower(heights.size(), 0); // 左侧更小柱子下标
vector<int> rightLower(heights.size(), 0); // 右侧更小柱子下标
// 记录的是左侧更小柱子的下标,因此给左侧第一根柱子初始化为-1,同时也统一了后续宽度的计算
leftLower[0] = -1;
for(int i = 1; i < heights.size(); i++) {
int l = i - 1;
// 如果遇到了比当前柱子高的柱子,直接调到那个更高柱子的左侧第一个更低柱子,利用之前的运算结果
while(l >= 0 && heights[l] >= heights[i]) l = leftLower[l];
leftLower[i] = l;
}
rightLower[heights.size() - 1] = heights.size(); // 同左侧数组的初始化
for(int j = heights.size() - 2; j >= 0; j--) {
int r = j + 1;
// r或者l的边界条件判断取决于初始化,如果l==-1或者r==size,都说明已经到头了,可以跳出while
while(r < heights.size() && heights[r] >= heights[j]) r = rightLower[r];
rightLower[j] = r;
}
for(int i = 0; i < heights.size(); i++) {
int w = rightLower[i] - leftLower[i] - 1;
sum = max(sum, w * heights[i]);
}
return sum;
}
};
单调栈:
这道题是找当前柱子左右侧的第一个更低柱子,两个更低柱子之间就可以组成一个以当前柱子为高度的矩形。这与接雨水正好是相反的,接雨水是要形成凹槽,这道题则是形成两边低中间高的形状,这样应该用单调递减栈。(从栈头到栈底)
具体情况的处理与接雨水是相似的,只需要反过来。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int sum = 0;
stack<int> st;
heights.insert(heights.begin(), 0);
heights.push_back(0);
st.push(0);
for(int i = 1; i < heights.size(); i++) {
if(heights[i] > heights[st.top()]) {
st.push(i);
}
else if(heights[i] == heights[st.top()]) {
st.pop(); // 这句有没有都行,不影响计算柱子两侧的第一个更低柱子
st.push(i);
}
else {
while(!st.empty() && heights[i] < heights[st.top()]) {
int mid = st.top(); // 记录中间的柱子,也是矩形的高度
st.pop();
if(!st.empty()) {
int w = i - st.top() - 1; // 栈顶的第二个元素肯定比刚出栈的小,这样就找到了两侧第一个更低的柱子,可以计算宽度
sum = max(sum, w * heights[mid]);
}
}
st.push(i);
}
}
return sum;
}
};
处理中与接雨水最不同的点在于在heights数组前后都添加了0。在末尾加0是为了解决整个柱高数组单调不减的情况,如果是[2,4,6,8]这样的柱高数组,不在末尾加0,就会一直满足单调栈条件不会触发计算面积的情况。加了0,就可以在最后对每一个柱子计算能形成的矩形的面积了。
在开头位置加0是为了解决单调减的柱高数组,如果是[8,6,4,2]的柱高数组,不前置0的话,先是将8入栈,当6入栈时不满足单调递减栈的条件,将8弹出栈,但此时栈已经为空,不会计算以8为高度的矩形面积,直接执行6入栈,后面的入栈操作也会是这样。只有前置了0,使栈不至于为空,才能考虑以每一个柱高勾勒矩形的面积。
前后加0,不会漏掉任何情况,同时我们也可以发现栈永远不会空!代码是可以简化的!