当前:LC85 · 最大矩形 · 首次出现于 Day 44 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC85 · Maximal Rectangle · 单调栈

最大矩形:每一行当成柱状图的底

全 1 矩形的底边一定落在某一行。把该行各列「向上连续 1 的根数」当成柱高,再对这排柱子跑一遍柱状图最大矩形。

heights[j] 表示第 j 列截至当前行、向上连续 1 的根数;遇 0 必须清零。对每一行的 heights 用单调栈求柱状图最大矩形,所有行取最大。时间 O(m·n),空间 O(n)。

时间 O(m·n)空间 O(n)结论先行 · 全文约 6 节
导读

给你一个只含 0 和 1 的二维矩阵,找出里面全是 1 的最大矩形,返回它的面积。矩形必须是整块、边与坐标轴平行,不能斜着挖、也不能中间夹 0。

主例是 4 行 5 列:第一行 1 0 1 0 0,第二行 1 0 1 1 1,第三行 1 1 1 1 1,第四行 1 0 0 1 0。最大的全 1 矩形在第二、三行的后三列,两行三列,面积 6。

枚举左上角和右下角再检查是否全 1,能做对,但是立方甚至四次方级比较。本文要回答:为什么可以把二维问题压成「每一行当底的柱状图」,以及主例里那块面积 6 是在哪一行、哪几根柱子上结算出来的。

每行当底,高度遇 0 清零

第一直觉是枚举所有子矩形。主例 4×5,左上、右下各有二十来个格子,组合已经上百,每个还要扫一遍是否全 1。结果能对,但重复劳动太多:同一个全 1 区域会被不同边界重复检查。

缺的不是「哪一块最大」这张最终成绩单,而是一个可以降维的观察。任意一个全 1 矩形,底边一定落在某一行。一旦底边定在第 i 行,这个矩形在每一列上的高度,就是从第 i 行向上、连续 1 能数到多高。于是二维问题变成:对每一个可能的底边行,先算出一排柱子高度,再求这排柱子能围出的最大矩形。后半句正是 LC84。

用数组 heights 记账,长度等于列数,一开始全 0。从上往下扫每一行。当前格是 1,这根柱子往上多长一格,heights[j] 加一;当前格是 0,这根柱子在这一层断了,heights[j] 必须清零——上面那些 1 再也接不到这一行当底的矩形里。清零是这条降维能成立的关键,不是可有可无的细节。

对更新后的 heights 跑一次柱状图最大矩形:以每根柱为高,左右找到第一根更矮的柱,宽乘高,取最大。这一行结算完,和历史最好比一比。所有行扫完,历史最好就是答案。每一行更新高度是 O(n),单调栈也是 O(n),一共 m 行,总时间 O(m·n),额外只存一排高度。

用手走主例。第 0 行读完,高度 [1,0,1,0,0],最高的矩形就是单根柱,面积 1。第 1 行:第一列 1 累成 2,第二列 0 保持 0,后三列 1 变成 [2,0,2,1,1]。后三根柱最低是 1,宽 3,面积 3,刷新纪录。

第 2 行整行都是 1,高度累成 [3,1,3,2,2]。演示场景停在这一行。以高 2 为顶,最后三根柱都够高,面积 6;以高 1 为顶,中间四根柱都够高,面积 4。这一行真正的最大值是 6,对应主例那块两行三列。

第 3 行读到 1 0 0 1 0,高度变成 [4,0,0,3,0]。中间两根柱被 0 砍断,本行最大是第一列那根高 4 的柱,面积 4,打不过已经记下的 6。答案停在 6。

只看某一行的 1、不向上累计,会漏掉跨行的矩形。主例若只看第 2 行五个 1,面积是 5,小于 6。遇 0 却不清零,第 3 行第二列上面其实已经断开,高度还会被错加成 2,后面算出来的矩形会含 0。所以「按行当底」和「遇 0 清零」必须绑在一起。

降维复用底边落在哪一行一旦固定,每列能长多高就被「向上连续 1」唯一确定。二维最大矩形因此拆成 m 次一维柱状图。LC84 的单调栈在这里原样复用,不必为矩阵另写一套扩宽逻辑。
4×5 矩阵
1
0
1
0
0
1
0
1
1
1
1
1
1
1
1
1
0
0
1
0
3
1
3
2
2
第 3 行高度 [3,1,3,2,2],LC84 得 4

Go:逐行柱状图

solution.goGo
func maximalRectangle(matrix [][]byte) int {
if len(matrix) == 0 { return 0 }
n := len(matrix[0])
heights := make([]int, n)
best := 0
for _, row := range matrix {
for j := 0; j < n; j++ {
if row[j] == '1' { heights[j]++ } else { heights[j] = 0 }
}
if a := largestArea(heights); a > best { best = a }
}
return best
}

1heights 只存一排,表示「以当前行为底、每列向上连续 1 的高度」。空矩阵直接返回 0。

2当前格是 1 就累加,是 0 必须清零。不清零会把已经断开的 1 接到下一层矩形里。

3largestArea 就是 LC84:对当前这排柱子求最大矩形。主例在第 2 行 heights=[3,1,3,2,2] 上得到 6,第 3 行得不到更大的数。

总结

每一行当柱状图的底,向上数连续 1 当柱高,套 LC84;主例答案 6。

  • 全 1 矩形的底边必落在某一行。heights[j] 遇 0 清零,否则会把断开的 1 接到新底边上。
  • 主例第 2 行高度 [3,1,3,2,2],最后三根柱围出面积 6;第 3 行被 0 砍断,全局仍是 6。
  • m 行各跑一次 O(n) 单调栈,时间 O(m·n),额外空间一排高度。
同族题目
LC84柱状图中最大的矩形LC221最大正方形LC1504统计全 1 子矩形