LeetCode 84 & 85.柱状图最大矩形与最大矩形
1. 引言
LeetCode 84 题「柱状图中最大的矩形」是经典的单调栈应用题,而 85 题「最大矩形」则是它的二维扩展------在 0/1 矩阵中找出只包含 1 的最大矩形。85 题的常用解法正是将矩阵逐行转化为柱状图的高度,然后对每一行调用 84 题的解法。本文将详细解释这两道题的代码,并梳理它们之间的联系。
2. LeetCode 84:柱状图中最大的矩形
2.1 题目描述
给定 n 个非负整数表示柱状图中每个柱子的高度,每个柱子宽度为 1,求该柱状图中能勾勒出的最大矩形面积。
2.2 解题思路
使用单调栈,对每个柱子找到其左右两边第一个比它矮的柱子。
left[i]:左边第一个高度小于h[i]的柱子的下标(若不存在则为 -1)right[i]:右边第一个高度小于h[i]的柱子的下标(若不存在则为 n)
则当前柱子能形成的最大矩形面积为h[i] * (right[i] - left[i] - 1)。
遍历所有柱子取最大值即可。
2.3 代码实现
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& h) {
int n = h.size();
vector<int> left(n), right(n);
stack<int> st;
// 计算左边界
for(int i = 0; i < n; i++) {
while(!st.empty() && h[st.top()] >= h[i]) st.pop();
left[i] = st.empty() ? -1 : st.top();
st.push(i);
}
// 清空栈,计算右边界
while(!st.empty()) st.pop(); // 或 st = stack<int>();
for(int i = n-1; i >= 0; i--) {
while(!st.empty() && h[st.top()] >= h[i]) st.pop();
right[i] = st.empty() ? n : st.top();
st.push(i);
}
int res = 0;
for(int i = 0; i < n; i++) {
res = max(res, h[i] * (right[i] - left[i] - 1));
}
return res;
}
};
2.4 代码解释
- 单调栈维护 :栈中存放柱子的下标,对应的高度保持严格递增(实际弹出条件是 ≥,保证栈内高度递增)。
- 左边界计算:从左向右遍历,弹出所有高度 ≥ 当前柱子的栈顶,剩下的栈顶就是左边第一个更矮的柱子,若栈空则左边界为 -1。
- 右边界计算:从右向左遍历,同理。
- 面积计算:对每个柱子计算可能的最大面积并更新答案。
2.5 复杂度分析
- 时间复杂度:O(n),每个元素入栈、出栈各一次。
- 空间复杂度:O(n),栈和左右数组。
3. LeetCode 85:最大矩形
3.1 题目描述
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
3.2 解题思路
将矩阵的每一行作为底边,向上累积连续 1 的个数 ,得到该行的高度数组。
例如,height[i][j] 表示从第 i 行向上(包括 i 行)连续 1 的个数,若 matrix[i][j] == 0 则高度归零。
这样,以第 i 行为底边的所有可能矩形就等价于一个柱状图,其柱子高度为 height[i][j]。
对每一行调用 84 题的 largestRectangleArea,取所有行的最大值即为答案。
3.3 代码实现
cpp
class Solution {
public:
// 复用 84 题的代码
int largestRectangleArea(vector<int>& h) {
int n = h.size();
vector<int> left(n), right(n);
stack<int> st;
for(int i = 0; i < n; i++) {
while(!st.empty() && h[st.top()] >= h[i]) st.pop();
left[i] = st.empty() ? -1 : st.top();
st.push(i);
}
while(!st.empty()) st.pop();
for(int i = n-1; i >= 0; i--) {
while(!st.empty() && h[st.top()] >= h[i]) st.pop();
right[i] = st.empty() ? n : st.top();
st.push(i);
}
int res = 0;
for(int i = 0; i < n; i++) {
res = max(res, h[i] * (right[i] - left[i] - 1));
}
return res;
}
int maximalRectangle(vector<vector<char>>& matrix) {
if(matrix.empty() || matrix[0].empty()) return 0;
int m = matrix.size(), n = matrix[0].size();
// 构造高度数组
vector<vector<int>> h(m, vector<int>(n, 0));
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(matrix[i][j] == '1') {
h[i][j] = (i == 0) ? 1 : h[i-1][j] + 1;
} // 否则保持 0
}
}
int res = 0;
for(int i = 0; i < m; i++) {
res = max(res, largestRectangleArea(h[i]));
}
return res;
}
};
3.4 代码解释
- 高度数组
h:h[i][j]记录第 i 行第 j 列向上连续 1 的个数。若当前为 '1',则累加上一行的高度(如果 i=0 则直接为 1);若为 '0' 则高度置 0。 - 逐行调用 84 题函数 :对每一行的
h[i](一维数组)计算柱状图最大矩形面积,并更新全局最大值。 - 为什么可以直接调用 84 题:每一行的高度数组正好对应一个柱状图,矩形必须连续且高度受限于区间内的最小高度,这与 84 题完全一致。
3.5 复杂度分析
- 时间复杂度:O(m×n)。构造高度数组需要 O(m×n),每行调用一次 84 题的 O(n) 算法,共 m 行,总 O(m×n)。
- 空间复杂度:O(m×n) 存储高度数组,但可以优化为 O(n) 滚动数组(此处未优化)。
4. 总结:两题的联系与区别
| 题目 | 核心思想 | 关键点 |
|---|---|---|
| 84 | 单调栈求每个柱子左右第一个更矮的位置 | 一维问题,不可重排 |
| 85 | 将二维矩阵逐行转化为一维柱状图,每行调用 84 题 | 二维问题,利用高度累积,不可重排 |
联系 :85 题的核心技巧是高度累积,把二维问题降为一维,然后复用 84 题的高效解法。这种思路在矩阵类问题中非常常见(如统计全 1 正方形等)。
区别:84 题是基础算法题,85 题是它的应用扩展。掌握 84 题的单调栈解法,是理解 85 题的前提。