面积最大矩形的高度一定是 heights 中的元素。这可以用反证法证明:假如高度不在 heights 中,比如 4,那我们可以增加高度直到触及某根柱子的顶部,比如增加到 5,由于矩形底边长不变,高度增加,我们得到了面积更大的矩形,矛盾,所以面积最大矩形的高度一定是 heights 中的元素。
class Solution {
public:
int largestRectangleArea(vector<int> &heights) {
int n = heights.size();
// 1️⃣ 记录每根柱子左边第一个比它小的柱子索引,初始化为 -1(表示左边无更小柱子)
vector<int> left(n, -1);
stack<int> st; // 用单调递增栈来找左边第一个更小元素
for (int i = 0; i < n; i++) {
// 当前柱子高度 <= 栈顶柱子高度,就一直弹出(因为我们要找的是第一个比它小的)
while (!st.empty() && heights[i] <= heights[st.top()]) {
st.pop();
}
// 如果栈不为空,说明当前栈顶是左边第一个比它小的柱子
if (!st.empty()) {
left[i] = st.top();
}
// 当前柱子的索引入栈
st.push(i);
}
// 2️⃣ 记录每根柱子右边第一个比它小的柱子索引,初始化为 n(表示右边无更小柱子)
vector<int> right(n, n);
st = stack<int>(); // 清空栈,重新使用
for (int i = n - 1; i >= 0; i--) {
// 当前柱子高度 <= 栈顶柱子高度,就一直弹出
while (!st.empty() && heights[i] <= heights[st.top()]) {
st.pop();
}
// 如果栈不为空,说明当前栈顶是右边第一个比它小的柱子
if (!st.empty()) {
right[i] = st.top();
}
// 当前柱子的索引入栈
st.push(i);
}
// 3️⃣ 遍历每根柱子,计算最大矩形面积
int ans = 0;
for (int i = 0; i < n; i++) {
int width = right[i] - left[i] - 1; // 可以扩展的宽度
int area = heights[i] * width; // 面积 = 高 × 宽
ans = max(ans, area); // 更新最大面积
}
return ans; // 返回最大面积
}
};
🔁 快速回顾单调栈逻辑
• 我们利用 单调递增栈 来维护柱子的索引,使得栈内元素对应的高度是递增的。
• 一旦遇到比栈顶小的高度,就可以弹出栈顶,说明当前柱子就是栈顶柱子的右边第一个小值。
🧠 可以这么记:
每根柱子能扩展的最大宽度 = 它左边第一个比它小的柱子到右边第一个比它小的柱子之间的距离(不含两端),乘上当前柱子的高度就是该柱子能围成的最大矩形。
好的!我们现在通过一个具体例子来详细运行一次:
🎯 题目:柱状图中最大的矩形面积
输入:
heights = [2, 1, 5, 6, 2, 3]
我们将用这个数组来一步步执行你提供的 largestRectangleArea 函数,理解每一步的计算过程。
✅ 第一步:求每个柱子左边第一个小于它的柱子(left[])
初始化:
left = [-1, -1, -1, -1, -1, -1]
stack = empty
遍历 heights:
i = 0, height = 2
• 栈空,left[0] = -1
• 入栈:[0]
i = 1, height = 1
• 栈顶是 0,height[0] = 2 > 1 → 弹出
• 栈空,left[1] = -1
• 入栈:[1]
i = 2, height = 5
• 栈顶是 1,height[1] = 1 < 5 → left[2] = 1
• 入栈:[1, 2]
i = 3, height = 6
• 栈顶是 2,height[2] = 5 < 6 → left[3] = 2
• 入栈:[1, 2, 3]
i = 4, height = 2
• 栈顶是 3,height[3] = 6 > 2 → 弹出
• 栈顶是 2,height[2] = 5 > 2 → 弹出
• 栈顶是 1,height[1] = 1 < 2 → left[4] = 1
• 入栈:[1, 4]
i = 5, height = 3
• 栈顶是 4,height[4] = 2 < 3 → left[5] = 4
• 入栈:[1, 4, 5]
最终:
left = [-1, -1, 1, 2, 1, 4]
✅ 第二步:求每个柱子右边第一个小于它的柱子(right[])
初始化:
right = [6, 6, 6, 6, 6, 6]
stack = empty
遍历 heights 从右到左:
i = 5, height = 3
• 栈空 → right[5] = 6
• 入栈:[5]
i = 4, height = 2
• 栈顶是 5,height[5] = 3 > 2 → 弹出
• 栈空 → right[4] = 6
• 入栈:[4]
i = 3, height = 6
• 栈顶是 4,height[4] = 2 < 6 → right[3] = 4
• 入栈:[4, 3]
i = 2, height = 5
• 栈顶是 3,height[3] = 6 > 5 → 弹出
• 栈顶是 4,height[4] = 2 < 5 → right[2] = 4
• 入栈:[4, 2]
i = 1, height = 1
• 栈顶是 2,height[2] = 5 > 1 → 弹出
• 栈顶是 4,height[4] = 2 > 1 → 弹出
• 栈空 → right[1] = 6
• 入栈:[1]
i = 0, height = 2
• 栈顶是 1,height[1] = 1 < 2 → right[0] = 1
• 入栈:[1, 0]
最终:
right = [1, 6, 4, 4, 6, 6]
✅ 第三步:计算每根柱子能构成的最大矩形面积
area[i] = heights[i] * (right[i] - left[i] - 1)
分别计算:
i | heights[i] | left[i] | right[i] | 宽度 = right - left - 1 | 面积 |
---|---|---|---|---|---|
0 | 2 | -1 | 1 | 1 | 2 × 1 = 2 |
1 | 1 | -1 | 6 | 6 | 1 × 6 = 6 |
2 | 5 | 1 | 4 | 2 | 5 × 2 = 10 |
3 | 6 | 2 | 4 | 1 | 6 × 1 = 6 |
4 | 2 | 1 | 6 | 4 | 2 × 4 = 8 |
5 | 3 | 4 | 6 | 1 | 3 × 1 = 3 |
最大面积:max = 10
✅ 最终结果
return 10;
🔚 总结
• left[i] 是左边第一个小于 heights[i] 的柱子索引;
• right[i] 是右边第一个小于 heights[i] 的柱子索引;
• (right[i] - left[i] - 1) 是以当前柱子为高,能扩展的最大宽度;
• 逐个计算出面积,取最大值返回。
如果你想,我可以帮你画出这个例子的柱状图 + 每根柱子的扩展区域,更直观看懂。需要吗?