题目描述:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10示例 2:
输入: heights = [2,4] 输出: 4
解题思路:
方法一:单调栈
核心洞察:
对于每根柱子,如果以它为最矮的柱子,能扩展的最大宽度是多少?
-
左边界:左边第一个比它矮的柱子的下标
-
右边界:右边第一个比它矮的柱子的下标
-
宽度 = 右边界 - 左边界 - 1
-
面积 = 高度 × 宽度
为什么用单调栈?
单调栈可以在 O(n) 时间内找到每个元素左边和右边第一个比它小的元素。
-
维护一个单调递增栈(栈底到栈顶递增)
-
当遇到比栈顶矮的柱子时,说明栈顶柱子的右边界找到了
-
栈顶下面的元素就是它的左边界
具体过程示例:
heights = [2, 1, 5, 6, 2, 3]
i=0, h=2: 栈空,压入 → 栈 [0]
i=1, h=1: 1 < 2(栈顶0),弹出0
左边界 = -1(栈空),右边界 = 1
宽度 = 1 - (-1) - 1 = 1,面积 = 2 × 1 = 2
栈空,压入1 → 栈 [1]
i=2, h=5: 5 > 1,压入 → 栈 [1, 2]
i=3, h=6: 6 > 5,压入 → 栈 [1, 2, 3]
i=4, h=2: 2 < 6(栈顶3),弹出3
左边界 = 2(栈顶2),右边界 = 4
宽度 = 4 - 2 - 1 = 1,面积 = 6 × 1 = 6
2 < 5(栈顶2),弹出2
左边界 = 1(栈顶1),右边界 = 4
宽度 = 4 - 1 - 1 = 2,面积 = 5 × 2 = 10 ✅
2 > 1(栈顶1),停止,压入4 → 栈 [1, 4]
i=5, h=3: 3 > 2,压入 → 栈 [1, 4, 5]
遍历结束,处理栈中剩余元素:
弹出5: 左边界 = 4,右边界 = 6
宽度 = 6 - 4 - 1 = 1,面积 = 3 × 1 = 3
弹出4: 左边界 = 1,右边界 = 6
宽度 = 6 - 1 - 1 = 4,面积 = 2 × 4 = 8
弹出1: 左边界 = -1,右边界 = 6
宽度 = 6 - (-1) - 1 = 6,面积 = 1 × 6 = 6
最大面积 = 10 ✅
代码实现:
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
stack<int> stk; // 存下标,对应高度单调递增
int maxArea = 0;
for (int i = 0; i <= n; i++) {
// 在末尾加一个高度为0的哨兵,确保所有柱子都被处理
int h = (i == n) ? 0 : heights[i];
while (!stk.empty() && h < heights[stk.top()]) {
int height = heights[stk.top()];
stk.pop();
// 左边界:栈顶元素(如果栈空,左边界为-1)
int left = stk.empty() ? -1 : stk.top();
// 右边界:当前下标 i
int width = i - left - 1;
maxArea = max(maxArea, height * width);
}
stk.push(i);
}
return maxArea;
}
};
关键细节:
1. 为什么在末尾加一个高度为0的哨兵?
cpp
int h = (i == n) ? 0 : heights[i];
-
如果不加哨兵,遍历结束后栈中还有元素,需要额外处理
-
加一个高度为0的哨兵,确保所有柱子都会被弹出计算
-
因为0比任何柱子都矮,会触发所有柱子的弹出
2. 为什么用 h < heights[stk.top()] 而不是 <=?
-
用
<时,相等高度的柱子会保留在栈中,最终一起计算 -
用
<=时,相等高度的柱子会提前弹出,可能导致宽度计算错误 -
推荐用
<
3. 宽度计算
cpp
int left = stk.empty() ? -1 : stk.top();
int width = i - left - 1;
-
左边界:栈顶元素(弹出后的新栈顶),如果栈空则为 -1
-
右边界 :当前下标
i -
宽度 = 右边界 - 左边界 - 1
4. 面积计算
cpp
int height = heights[stk.top()]; // 弹出前的高度
maxArea = max(maxArea, height * width);
-
高度:弹出柱子的高度
-
宽度 :
i - left - 1
复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 每个下标最多入栈出栈各一次 |
| 空间复杂度 | O(n) | 栈最多存储 n 个下标 |
方法二:左右边界预处理(O(n) 空间)
先用单调栈预处理每个柱子的左右边界,再计算面积。
代码实现:
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> left(n), right(n);
stack<int> stk;
// 找左边界
for (int i = 0; i < n; i++) {
while (!stk.empty() && heights[stk.top()] >= heights[i]) {
stk.pop();
}
left[i] = stk.empty() ? -1 : stk.top();
stk.push(i);
}
// 清空栈
while (!stk.empty()) stk.pop();
// 找右边界
for (int i = n - 1; i >= 0; i--) {
while (!stk.empty() && heights[stk.top()] >= heights[i]) {
stk.pop();
}
right[i] = stk.empty() ? n : stk.top();
stk.push(i);
}
// 计算面积
int maxArea = 0;
for (int i = 0; i < n; i++) {
maxArea = max(maxArea, heights[i] * (right[i] - left[i] - 1));
}
return maxArea;
}
};
复杂度:
时间 O(n),空间 O(n)
两种方法对比:
| 方法 | 时间复杂度 | 空间复杂度 | 推荐度 |
|---|---|---|---|
| 一次遍历单调栈 | O(n) | O(n) | ⭐⭐⭐⭐⭐ |
| 左右边界预处理 | O(n) | O(n) | ⭐⭐⭐⭐ |

