最大矩形 图解题解
全由 1 组成的最大矩形怎么找?逐行把矩阵变成柱状图,再用单调栈一次扫完,O(mn) 搞定困难题。
把矩阵从上往下逐行「压扁」:每往下一行,统计每一列往上连续有几个 1,就得到一排柱状图高度——本格是 1 就在上一行高度上加 1,是 0 就清零。然后对这排柱子用单调栈求最大矩形面积,新来的柱子比栈顶矮时把栈顶弹出结算。每行重复一次,做 m 次,最终最大值就是答案。把二维难题拆成 m 次一维单调栈,难度降一个等级。
这道题到底在问什么
- 输入
- 4×5 矩阵(见下)
- 输出
- 6
最优解:为什么这么做
一句话答案:LeetCode 85 最大矩形的标准解法是逐行转化成柱状图:用 heights[c] 记录第 c 列到当前行为止连续 1 的个数(遇 0 清零),把每一行当作底、对 heights 跑一遍 LeetCode 84 柱状图最大矩形的单调栈,所有行的结果取最大。时间 O(R×C)、空间 O(C)。
最大矩形这道题在问什么
给一个每格是 0 或 1 的矩阵,要框出一块全部由 1 组成的矩形,让面积最大。直接在二维里暴力枚举矩形,左右上下四条边随便组合就是 O(R²C²) 个候选,每个还要验证内部全 1,完全不可行。这题的价值就在于那一步降维转化。
为什么二维矩阵能压成柱状图
关键观察:任何一块全 1 矩形都有一条确定的底边,落在某一行上。反过来,固定「以第 r 行为底」之后,矩形在每一列能长多高,受限于该列从第 r 行向上连续 1 的个数——把这个数看成柱子的高度,第 r 行为底的所有全 1 矩形,恰好对应这排柱子构成的柱状图里的矩形。
于是原问题被拆成 R 个子问题:每一行作底生成一张柱状图,求柱状图最大矩形(这正是 LeetCode 84),所有行的答案取最大。因为每块矩形都有底行,逐行枚举不会漏掉任何候选。
heights 数组为什么遇 1 加一、遇 0 清零
heights[c] 的定义是:以当前行为底,第 c 列向上数连续 1 的个数。从上一行推到这一行只需一步增量更新:当前格是 1,柱子接着长高,heights[c] 加 1;当前格是 0,这一列被拦腰斩断,heights[c] 必须清零——0 上方那些 1 与本行不连通,不能参与以本行为底的矩形。清零这刀不砍,柱高虚高,答案就会把断开的区域误算进去。增量维护让每行的柱状图只花 O(C) 就能建好,不必每行都从头向上数。
每一行的柱状图怎么用单调栈结算
行内就是标准的 LeetCode 84 解法:维护柱高递增的栈(存下标),当前柱比栈顶矮时弹出栈顶结算——以它为高,宽度是当前下标与弹出后新栈顶之间的格数 i - left - 1,即左右两侧第一根更矮柱夹出的范围;末尾追加一根高 0 的哨兵柱,把扫完后还留在栈里的柱子全部逼出来结算。以题目的 4×5 示例矩阵为例,答案 6 出现在以第 2 行为底的柱状图 [3,1,3,2,2] 里:高 2、宽 3,覆盖第 2 到 4 列。
复杂度怎么算,哪些地方容易错
时间 O(R×C):每行 O(C) 更新 heights 加 O(C) 单调栈(每根柱进出栈各一次),共 R 行。空间 O(C):heights 数组与栈都只随列数增长,不需要二维辅助结构。
三个高频错误对应转化链上的三个环节:遇 0 不清零,柱状图本身就是错的;只算某一行、忘了对所有行取全局最大,而最大矩形的底行事先无从知晓;行内忘加末尾哨兵,栈里递增的尾巴没被结算,会漏掉答案。
▶ 动画逐步走查(共 67 步)——想跟着动画一帧帧对照就展开
- 3核心一句:0/1 矩阵的最大矩形 = 「逐行累加成柱状图 + 每行求柱状图最大矩形」的最大值。下面一行一行演示。
- 4处理第 0 行:把它当柱状图的底。每一列从这行往上数连续的 1,就是这根柱子的高 —— heights = [1, 0, 1, 0, 0](遇 0 的列被斩断清零)。下面对这张柱状图求最大矩形。
- 5第 0 行:轮到柱 0(高 1)。栈空,没有更矮的栈顶要结算,直接入栈。
- 6第 0 行:没有更矮的栈顶要结算,把柱 0(高 1)压入栈顶,栈仍保持递增。
- 7第 0 行:轮到柱 1(高 0)。看栈顶柱 0(高 1):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 8第 0 行:弹出柱 0(高 1)。它向左只能扩到最左端,向右扩到柱 1(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 1 × 宽 1 = 1。比之前的 0 大,全局最大更新为 1。
- 9第 0 行:没有更矮的栈顶要结算,把柱 1(高 0)压入栈顶,栈仍保持递增。
- 10第 0 行:轮到柱 2(高 1)。看栈顶柱 1(高 0):当前不比栈顶矮,栈仍递增,直接入栈。
- 11第 0 行:没有更矮的栈顶要结算,把柱 2(高 1)压入栈顶,栈仍保持递增。
- 12第 0 行:轮到柱 3(高 0)。看栈顶柱 2(高 1):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 13第 0 行:弹出柱 2(高 1)。它向左只能扩到柱 1(更矮挡住),向右扩到柱 3(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 1 × 宽 1 = 1。不及全局最大 1,最大不变。
- 14第 0 行:没有更矮的栈顶要结算,把柱 3(高 0)压入栈顶,栈仍保持递增。
- 15第 0 行:轮到柱 4(高 0)。看栈顶柱 3(高 0):当前不比栈顶矮,栈仍递增,直接入栈。
- 16第 0 行:没有更矮的栈顶要结算,把柱 4(高 0)压入栈顶,栈仍保持递增。
- 17第 0 行:轮到末尾哨兵柱(高 0,灰色)。它比任何真实柱都矮,逼着把栈里没结算的柱子全弹出算一遍。
- 18第 0 行扫描结束,哨兵入栈,本行所有柱都结算完。此刻全局最大面积 = 1。
- 19处理第 1 行:把它当柱状图的底。每一列从这行往上数连续的 1,就是这根柱子的高 —— heights = [2, 0, 2, 1, 1](遇 0 的列被斩断清零)。下面对这张柱状图求最大矩形。
- 20第 1 行:轮到柱 0(高 2)。栈空,没有更矮的栈顶要结算,直接入栈。
- 21第 1 行:没有更矮的栈顶要结算,把柱 0(高 2)压入栈顶,栈仍保持递增。
- 22第 1 行:轮到柱 1(高 0)。看栈顶柱 0(高 2):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 23第 1 行:弹出柱 0(高 2)。它向左只能扩到最左端,向右扩到柱 1(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 2 × 宽 1 = 2。比之前的 1 大,全局最大更新为 2。
- 24第 1 行:没有更矮的栈顶要结算,把柱 1(高 0)压入栈顶,栈仍保持递增。
- 25第 1 行:轮到柱 2(高 2)。看栈顶柱 1(高 0):当前不比栈顶矮,栈仍递增,直接入栈。
- 26第 1 行:没有更矮的栈顶要结算,把柱 2(高 2)压入栈顶,栈仍保持递增。
- 27第 1 行:轮到柱 3(高 1)。看栈顶柱 2(高 2):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 28第 1 行:弹出柱 2(高 2)。它向左只能扩到柱 1(更矮挡住),向右扩到柱 3(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 2 × 宽 1 = 2。不及全局最大 2,最大不变。
- 29第 1 行:没有更矮的栈顶要结算,把柱 3(高 1)压入栈顶,栈仍保持递增。
- 30第 1 行:轮到柱 4(高 1)。看栈顶柱 3(高 1):当前不比栈顶矮,栈仍递增,直接入栈。
- 31第 1 行:没有更矮的栈顶要结算,把柱 4(高 1)压入栈顶,栈仍保持递增。
- 32第 1 行:轮到末尾哨兵柱(高 0,灰色)。它比任何真实柱都矮,逼着把栈里没结算的柱子全弹出算一遍。
- 33第 1 行:弹出柱 4(高 1)。它向左只能扩到柱 3(更矮挡住),向右扩到柱 5(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 1 × 宽 1 = 1。不及全局最大 2,最大不变。
- 34第 1 行:弹出柱 3(高 1)。它向左只能扩到柱 1(更矮挡住),向右扩到柱 5(更矮挡住),高亮的 3 根就是它能罩住的最宽范围。矩形 = 高 1 × 宽 3 = 3。比之前的 2 大,全局最大更新为 3。
- 35第 1 行扫描结束,哨兵入栈,本行所有柱都结算完。此刻全局最大面积 = 3。
- 36处理第 2 行:把它当柱状图的底。每一列从这行往上数连续的 1,就是这根柱子的高 —— heights = [3, 1, 3, 2, 2](遇 0 的列被斩断清零)。下面对这张柱状图求最大矩形。
- 37第 2 行:轮到柱 0(高 3)。栈空,没有更矮的栈顶要结算,直接入栈。
- 38第 2 行:没有更矮的栈顶要结算,把柱 0(高 3)压入栈顶,栈仍保持递增。
- 39第 2 行:轮到柱 1(高 1)。看栈顶柱 0(高 3):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 40第 2 行:弹出柱 0(高 3)。它向左只能扩到最左端,向右扩到柱 1(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 3 × 宽 1 = 3。不及全局最大 3,最大不变。
- 41第 2 行:没有更矮的栈顶要结算,把柱 1(高 1)压入栈顶,栈仍保持递增。
- 42第 2 行:轮到柱 2(高 3)。看栈顶柱 1(高 1):当前不比栈顶矮,栈仍递增,直接入栈。
- 43第 2 行:没有更矮的栈顶要结算,把柱 2(高 3)压入栈顶,栈仍保持递增。
- 44第 2 行:轮到柱 3(高 2)。看栈顶柱 2(高 3):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 45第 2 行:弹出柱 2(高 3)。它向左只能扩到柱 1(更矮挡住),向右扩到柱 3(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 3 × 宽 1 = 3。不及全局最大 3,最大不变。
- 46第 2 行:没有更矮的栈顶要结算,把柱 3(高 2)压入栈顶,栈仍保持递增。
- 47第 2 行:轮到柱 4(高 2)。看栈顶柱 3(高 2):当前不比栈顶矮,栈仍递增,直接入栈。
- 48第 2 行:没有更矮的栈顶要结算,把柱 4(高 2)压入栈顶,栈仍保持递增。
- 49第 2 行:轮到末尾哨兵柱(高 0,灰色)。它比任何真实柱都矮,逼着把栈里没结算的柱子全弹出算一遍。
- 50第 2 行:弹出柱 4(高 2)。它向左只能扩到柱 3(更矮挡住),向右扩到柱 5(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 2 × 宽 1 = 2。不及全局最大 3,最大不变。
- 51第 2 行:弹出柱 3(高 2)。它向左只能扩到柱 1(更矮挡住),向右扩到柱 5(更矮挡住),高亮的 3 根就是它能罩住的最宽范围。矩形 = 高 2 × 宽 3 = 6。比之前的 3 大,全局最大更新为 6。
- 52第 2 行:弹出柱 1(高 1)。它向左只能扩到最左端,向右扩到柱 5(更矮挡住),高亮的 5 根就是它能罩住的最宽范围。矩形 = 高 1 × 宽 5 = 5。不及全局最大 6,最大不变。
- 53第 2 行扫描结束,哨兵入栈,本行所有柱都结算完。此刻全局最大面积 = 6。
- 54处理第 3 行:把它当柱状图的底。每一列从这行往上数连续的 1,就是这根柱子的高 —— heights = [4, 0, 0, 3, 0](遇 0 的列被斩断清零)。下面对这张柱状图求最大矩形。
- 55第 3 行:轮到柱 0(高 4)。栈空,没有更矮的栈顶要结算,直接入栈。
- 56第 3 行:没有更矮的栈顶要结算,把柱 0(高 4)压入栈顶,栈仍保持递增。
- 57第 3 行:轮到柱 1(高 0)。看栈顶柱 0(高 4):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 58第 3 行:弹出柱 0(高 4)。它向左只能扩到最左端,向右扩到柱 1(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 4 × 宽 1 = 4。不及全局最大 6,最大不变。
- 59第 3 行:没有更矮的栈顶要结算,把柱 1(高 0)压入栈顶,栈仍保持递增。
- 60第 3 行:轮到柱 2(高 0)。看栈顶柱 1(高 0):当前不比栈顶矮,栈仍递增,直接入栈。
- 61第 3 行:没有更矮的栈顶要结算,把柱 2(高 0)压入栈顶,栈仍保持递增。
- 62第 3 行:轮到柱 3(高 3)。看栈顶柱 2(高 0):当前不比栈顶矮,栈仍递增,直接入栈。
- 63第 3 行:没有更矮的栈顶要结算,把柱 3(高 3)压入栈顶,栈仍保持递增。
- 64第 3 行:轮到柱 4(高 0)。看栈顶柱 3(高 3):当前更矮,栈顶再往右扩就被挡住,弹出它、以它为高算矩形。
- 65第 3 行:弹出柱 3(高 3)。它向左只能扩到柱 2(更矮挡住),向右扩到柱 4(更矮挡住),高亮的 1 根就是它能罩住的最宽范围。矩形 = 高 3 × 宽 1 = 3。不及全局最大 6,最大不变。
- 66第 3 行:没有更矮的栈顶要结算,把柱 4(高 0)压入栈顶,栈仍保持递增。
- 67第 3 行:轮到末尾哨兵柱(高 0,灰色)。它比任何真实柱都矮,逼着把栈里没结算的柱子全弹出算一遍。
- 68第 3 行扫描结束,哨兵入栈,本行所有柱都结算完。此刻全局最大面积 = 6。
- 69所有行都求过一次柱状图最大矩形,全局最大出现在第 2 行:高 2、宽 3、覆盖列 2~4,面积 = 2 × 3 = 6。对应回原矩阵,就是那块全 1 的最大矩形。
⚠️ 容易写错的地方
✗ 错:遇 0 仍累加高度
✓ 对:遇 0 必须清零
0 把这一列的柱子拦腰斩断,上方的连续 1 不能跨过 0
✗ 错:每行各算各的、不取全局最大
✓ 对:所有行的矩形取最大
最大矩形可能出现在任意一行作底的柱状图里
✗ 错:柱状图忘记末尾哨兵
✓ 对:末尾补一根高 0
否则扫完后栈里递增的柱子没被结算,会漏算
完整代码(Python / C++ / Java)
Python
def maximalRectangle(matrix):
if not matrix: return 0
C = len(matrix[0])
heights = [0] * C # 逐行累加的柱高
best = 0
for row in matrix:
for c in range(C): # 遇 1 高度+1,遇 0 清零
heights[c] = heights[c] + 1 if row[c] == "1" else 0
best = max(best, largest(heights)) # LC84
return best
def largest(hs): # 柱状图最大矩形·单调递增栈
hs = hs + [0]; st = []; b = 0
for i, h in enumerate(hs):
while st and h < hs[st[-1]]:
top = st.pop()
left = st[-1] if st else -1
b = max(b, hs[top] * (i - left - 1))
st.append(i)
return bC++
int largest(vector<int> hs){ // 柱状图最大矩形
hs.push_back(0); stack<int> st; int b = 0;
for(int i = 0; i < (int)hs.size(); i++){
while(!st.empty() && hs[i] < hs[st.top()]){
int top = st.top(); st.pop();
int left = st.empty() ? -1 : st.top();
b = max(b, hs[top] * (i - left - 1));
}
st.push(i);
}
return b;
}
int maximalRectangle(vector<vector<char>>& m){
if(m.empty()) return 0;
int C = m[0].size(), best = 0;
vector<int> heights(C, 0);
for(auto& row : m){
for(int c = 0; c < C; c++)
heights[c] = row[c] == '1' ? heights[c] + 1 : 0;
best = max(best, largest(heights));
}
return best;
}Java
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0) return 0;
int C = matrix[0].length, best = 0;
int[] heights = new int[C]; // 逐行累加的柱高
for (char[] row : matrix) {
for (int c = 0; c < C; c++) // 遇 1 高度+1,遇 0 清零
heights[c] = row[c] == '1' ? heights[c] + 1 : 0;
best = Math.max(best, largest(heights)); // LC84
}
return best;
}
private int largest(int[] hs) { // 柱状图最大矩形·单调栈
int n = hs.length;
int[] h = new int[n + 1]; // 末尾哨兵 0
System.arraycopy(hs, 0, h, 0, n);
Deque<Integer> st = new ArrayDeque<>();
int b = 0;
for (int i = 0; i <= n; i++) {
while (!st.isEmpty() && h[i] < h[st.peek()]) {
int top = st.pop();
int left = st.isEmpty() ? -1 : st.peek();
b = Math.max(b, h[top] * (i - left - 1));
}
st.push(i);
}
return b;
}复杂度
时间
O(R×C)
每行 O(C) 累加 + O(C) 单调栈,共 R 行
空间
O(C)
heights 数组 + 单调栈各 O(C)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最大矩形 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么能把二维问题拆成逐行的柱状图?+
任何全 1 矩形必有一个「底行」。固定底行后,每列向上连续 1 的高度就是柱高,矩形宽就是连续若干列,于是退化成「柱状图最大矩形」。枚举每一行作底,就覆盖了所有矩形。
和 LC84「柱状图最大矩形」是什么关系?+
85 = 逐行构造柱状图 + 对每行调用 84 的单调栈解法,取所有行的最大值。84 是 85 的内核子过程。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最大矩形 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。