代码随想录算法训练营第六十四天_第十章_单调栈 | 84. 柱状图中最大的矩形、85. 最大矩形

文章详细介绍了如何解决LeetCode的84题和85题,即在柱状图中找到最大的矩形面积以及在二进制矩阵中找到只包含1的最大矩形。主要方法包括双指针、动态规划和单调栈,文章提供了多种解法的实现代码,并对每种方法的思路进行了详细解释。
摘要由CSDN通过智能技术生成

LeetCode 84. 柱状图中最大的矩形

        给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的 矩形的最大面积

视频讲解文章讲解https://programmercarl.com/0084.%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.html#%E5%8F%8C%E6%8C%87%E9%92%88%E8%A7%A3%E6%B3%95

  • 思路:
    • 双指针解法:对于每一个i,以heights[i]为矩形的长,找最大宽度,求得面积来更新 result
      • 从 i 出发,向左找第一个高度小于 heights[i] 的柱子,记其下标为 left
      • 从 i 出发,向右找第一个高度小于 heights[i] 的柱子,记其下标为 right
      • 最大宽度(left, right)👉right - left - 1
      • result = max(result, (right - left - 1) * heights[i]);
    • 动态规划解法:两个dp数组
      • 数组 minLeftIndex:记录每个柱子 左边第一个低于该柱子的下标
      • 数组 minRightIndex:记录每个柱子 右边第一个低于该柱子的下标
      • 注意:minLeftIndex vs 接雨水 maxLeft
        1. 左(右)边第一个低于 i 的柱子 vs 左边高的柱子
        2. 下标 vs 高度
        3. 循环查找 vs 直接由 maxLeft[i - 1] 与 i 高度取大
    • 单调栈解法
      • 从栈顶到栈底(高度)递减;
      • height[st.top()] == height[i],有两种处理思路:
        • 出栈旧下标,入栈新下标:相当于保留等高柱的最右一个
        • 直接令 i 入栈,栈中柱子高度不是严格单调递减:相当于以等高柱的最左一个计算实际的最大宽度,并更新result
      • 自己:
        • height[st.top()] > height[i]:right = i 
          • 以栈顶柱子高度为矩形的长:int h = height[st.top()];
          • 以栈顶第二个元素下标为 left,矩形的最大宽度(left, i)👉i - left - 1
          • 如果没有栈顶第二个元素,left 为 -1
        • 单独处理栈中剩余元素,height[st.top()] > 0,right = heights.size()
      • 代码随想录:数组首尾分别加入元素0
                左:直接处理原数组                                         右:数组首尾分别加入元素 0
        ​​for循环结束后                                                    for循环结束后
        需单独处理栈中剩余元素                                    所有元素都已经处理完
  • 代码:
// 双指针:
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;
            int h = heights[i];
            sum = max(sum, w * h);
        }
        return sum;
    }
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
// 动态规划:
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        vector<int> minLeftIndex(heights.size());
        vector<int> minRightIndex(heights.size());
        int size = heights.size();

        // 记录每个柱子 左边第一个小于该柱子的下标
        minLeftIndex[0] = -1; // 注意这里初始化,防止下面while死循环
        for (int i = 1; i < size; i++) {
            int t = i - 1;
            // 这里不是用if,而是不断向左寻找的过程
            while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t];
            minLeftIndex[i] = t;
        }
        // 记录每个柱子 右边第一个小于该柱子的下标
        minRightIndex[size - 1] = size; // 注意这里初始化,防止下面while死循环
        for (int i = size - 2; i >= 0; i--) {
            int t = i + 1;
            // 这里不是用if,而是不断向右寻找的过程
            while (t < size && heights[t] >= heights[i]) t = minRightIndex[t];
            minRightIndex[i] = t;
        }
        // 求和
        int result = 0;
        for (int i = 0; i < size; i++) {
            int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1);
            result = max(sum, result);
        }
        return result;
    }
};
// 单调栈:
// 版本一:从栈顶到栈底严格单调递减
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        heights.insert(heights.begin(), 0); // 数组头部加入元素0
        heights.push_back(0); // 数组尾部加入元素0
        st.push(0);
        int result = 0;
        // 第一个元素已经入栈,从下标1开始
        for (int i = 1; i < heights.size(); i++) {
            // 注意heights[i] 是和heights[st.top()] 比较 ,st.top()是下标
            if (heights[i] > heights[st.top()]) {
                st.push(i);
            } else if (heights[i] == heights[st.top()]) {
                st.pop(); // 这个可以加,可以不加,效果一样,思路不同
                st.push(i);
            } else {
                while (heights[i] < heights[st.top()]) { // 注意是while
                    int mid = st.top();
                    st.pop();
                    int left = st.top();
                    int right = i;
                    int w = right - left - 1;
                    int h = heights[mid];
                    result = max(result, w * h);
                }
                st.push(i);
            }
        }
        return result;
    }
};

// 精简版:合并情况一、二,从栈顶到栈底非严格单调递减(栈中可以有高度相同的柱子)
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        heights.insert(heights.begin(), 0); // 数组头部加入元素0
        heights.push_back(0); // 数组尾部加入元素0
        st.push(0);
        int result = 0;
        for (int i = 1; i < heights.size(); i++) {
            while (heights[i] < heights[st.top()]) {
                int mid = st.top();
                st.pop();
                int w = i - st.top() - 1;
                int h = heights[mid];
                result = max(result, w * h);
            }
            st.push(i);
        }
        return result;
    }
};

LeetCode 85. 最大矩形

        给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

  • 思路:
    • 二维转化为一维:计算以 当前行 为基准,柱子的高,代入84题中
    • 数组首尾分别加入元素0:不妨在初始化 sum 数组时,列数比 matrix 多 2,全初始化为 0,matrix[i][0] 对应 sum[i][1] ... matrix[i][j] 对应 sum[i][j + 1]
    • 对 sum 中的每一行,用单调栈处理(同84题)
    • 注意:原数组中存放的是 字符 '0' 和 字符 '1'
  • 代码:
class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int result = 0;
        // 前缀和
        vector<vector<int>> sum(matrix.size(), vector<int>(matrix[0].size() + 2, 0)); // 行数不变,每行首尾各加一个0
        // 初始化:注意matrix数组中存放的是字符'0'和字符'1'
        for (int j = 0; j < matrix[0].size(); ++j) {
            if (matrix[0][j] == '1') sum[0][j + 1] = 1;
        }
        // 通过matrix[i][j]计算sum[i][j + 1]
        for (int i = 1; i < matrix.size(); ++i) {
            for (int j = 0; j < matrix[0].size(); ++j) {
                if (matrix[i][j] == '1') {
                    sum[i][j + 1] = sum[i - 1][j + 1] + 1;
                }
            }
        }
        // 每行用单调栈处理
        for (int i = 0; i < sum.size(); ++i) {
            stack<int> st; // 存放下标,栈底↗栈顶
            st.push(0);
            for (int j = 1; j < sum[0].size(); ++j) {
                while (sum[i][st.top()] > sum[i][j]) {
                    int h = sum[i][st.top()];
                    st.pop();
                    int w = j - st.top() - 1;
                    result = max(result, h * w);
                }
                st.push(j);
            }
        }
        return result;
    }
};

单调栈总结:一般来说令下标入栈,可保存更多信息,方便求距离(宽度)

⭐找左边(或右边)第一个比当前元素的:

从栈顶到栈底递减

 循环处理 height[st.top()] > height[i],出栈,统计结果

⭐找左边(或右边)第一个比当前元素的:

从栈顶到栈底递增

 循环处理 height[st.top()] < height[i],出栈,统计结果

注意:for 循环结束后,对于栈中剩余元素:

        有些题可以不用单独处理,直接把 result 数组全初始化成找不到时对应的值即可;

        有些题则需要把栈中元素处理完(84题),可以提前在首尾各加一个0,把 for 循环和 栈中剩余元素的处理逻辑统一起来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值