【栈-5】84.柱状图中最大的矩形

题目描述:

给定 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) ⭐⭐⭐⭐
相关推荐
01二进制代码漫游日记1 小时前
C++之类和对象(上)02
c++
wzdark1 小时前
数据压缩算法的时间复杂度与压缩率权衡4
算法
小陈phd2 小时前
深入理解agent学习笔记(一)——现代agent介绍
人工智能·算法
渡我白衣2 小时前
HttpRequest与HttpResponse的实现
服务器·数据结构·c++·人工智能·tcp/ip·机器学习·caffe
刃神太酷啦2 小时前
前端入门第一课:HTML 基础语法 + 常用标签 + 实战全解
服务器·c语言·前端·javascript·css·c++·html
蒸蒸yyyyzwd2 小时前
八股学习笔记 day42
c++·八股
圣保罗的大教堂2 小时前
leetcode 1665. 完成所有任务的最少初始能量 中等
leetcode
凤年徐2 小时前
C++ 仿 muduo 高并发服务器:用户态缓冲区 Buffer 模块实现
linux·c++
晴天的雨.9924 小时前
【C++算法】和为s的两个数
开发语言·数据结构·c++·算法