力扣hot100之85:最大矩形
力扣hot100之85:最大矩形

LeetCode 85:最大矩形(Maximal Rectangle)题解

Maximal Rectangle 是 LeetCode Hot 100 里面一道非常经典的矩阵题。

这道题第一次看时,很多人都会有点发懵。因为题目给的是一个只包含 01 的二维矩阵,要求你在其中找到只包含 1 的最大矩形面积。

看起来像是在二维平面上直接找矩形,但如果真的按最直觉的方式去枚举:

  • 枚举左上角

  • 枚举右下角

  • 判断这个矩形内部是不是全是 1

那复杂度会非常高,几乎不可能通过。

这道题真正巧妙的地方在于:

它可以转化成很多次“柱状图中最大的矩形”。

而你给出的这份 Java 代码,正是这道题最经典的一种写法:

  1. 按行扫描矩阵

  2. 把每一行都转化成一个“柱状图高度数组”

  3. 对每一行对应的柱状图,求一次最大矩形面积

  4. 取所有行中的最大值

因为你在前一题已经写了“柱状图中最大的矩形”,所以这道题其实可以理解成:

在二维矩阵上套一层柱状图思想。

本文就结合这段代码,系统地讲一下这道题的思路。


题目介绍

给定一个仅包含 '0''1' 的二维矩阵 matrix,请你找出其中只包含 1 的最大矩形,并返回其面积。

例如:

matrix =
[
  ['1','0','1','0','0'],
  ['1','0','1','1','1'],
  ['1','1','1','1','1'],
  ['1','0','0','1','0']
]

在这个矩阵中,最大的只包含 1 的矩形面积是:

6

这道题最难的地方,不是“什么是矩形”,而是:

怎么高效判断某个大矩形是否全是 1,以及如何避免暴力枚举。


示例

示例 1

输入:
matrix =
[
  ['1','0','1','0','0'],
  ['1','0','1','1','1'],
  ['1','1','1','1','1'],
  ['1','0','0','1','0']
]

输出:6

其中最大的矩形可以是中间那一块连续的 1,面积为 6

示例 2

输入:matrix = []
输出:0

空矩阵中当然不存在矩形,所以返回 0


最直接的想法为什么不好?

如果不考虑优化,一个最直接的思路可能是:

  • 枚举矩形左上角

  • 枚举矩形右下角

  • 检查这个矩形区域内是否全部为 1

  • 如果是,就更新最大面积

但这种做法的代价非常大,因为:

  1. 矩形数量很多

  2. 每个矩形还要检查内部所有元素

所以整体复杂度会高得离谱。

因此,这道题真正高效的关键,不是“暴力找矩形”,而是找到一个更适合计算矩形面积的表示方式。


核心思路:按行构造柱状图

这道题最巧妙的地方就在这里。

我们从第一行开始往下看,把每一列“连续的 1 的高度”记录下来。

例如矩阵:

[
  ['1','0','1','0','0'],
  ['1','0','1','1','1'],
  ['1','1','1','1','1']
]

我们逐行构造高度数组:

第 1 行后

height = [1,0,1,0,0]

第 2 行后

如果当前位置还是 '1',说明这一列的连续高度可以继续加一; 如果是 '0',说明连续高度中断,重置为 0。

所以变成:

height = [2,0,2,1,1]

第 3 行后

继续更新:

height = [3,1,3,2,2]

你会发现,这个 height 数组就像是一个柱状图。 而“以当前行为底”的最大只含 1 矩形,恰好就等价于:

这个柱状图中的最大矩形面积。

所以整道题就转化成了:

  • 对每一行构造一个柱状图

  • 然后调用“柱状图最大矩形”的解法

这正是你代码的整体思路。


Java 代码

下面是你给出的代码,我这里保留原始逻辑,并加上注释:

class Solution {
    public int maximalRectangle(char[][] matrix) {
        if (matrix.length == 0) return 0;

        // height[col] 表示当前行作为底时,第 col 列连续 1 的高度
        int[] height = new int[matrix[0].length];
        int res = 0;

        for (char[] chars : matrix) {
            // 更新当前行对应的柱状图高度
            for (int col = 0; col < chars.length; col++) {
                if (chars[col] == '1') height[col]++;
                else height[col] = 0;
            }

            // 对当前柱状图求最大矩形面积
            res = Math.max(res, largesRectangleArea(height));
        }

        return res;
    }

    public int largesRectangleArea(int[] heights) {
        Stack<Integer> s = new Stack<>();
        int[] lefLower = new int[heights.length];
        int[] rigLower = new int[heights.length];

        Arrays.fill(rigLower, heights.length);

        for (int i = 0; i < heights.length; i++) {
            while (!s.empty() && heights[s.peek()] > heights[i]) {
                rigLower[s.pop()] = i;
            }
            lefLower[i] = s.empty() ? -1 : s.peek();
            s.push(i);
        }

        int res = 0;
        for (int i = 0; i < heights.length; i++) {
            res = Math.max(res, (rigLower[i] - lefLower[i] - 1) * heights[i]);
        }

        return res;
    }
}

代码拆解

这段代码其实可以分成两部分来看:

  1. maximalRectangle:负责把二维矩阵转成一行行柱状图

  2. largesRectangleArea:负责求每个柱状图中的最大矩形

你前一题已经写过柱状图最大矩形,所以这题的真正重点,其实是第一部分——如何把二维问题转化成一维柱状图问题。


1. 为什么空矩阵直接返回 0?

if (matrix.length == 0) return 0;

因为如果矩阵没有任何行,那自然不可能形成矩形,答案就是 0。

这是一个基础边界情况,必须先处理掉。


2. height 数组表示什么?

int[] height = new int[matrix[0].length];

这个数组非常关键。

它表示:

当前这一行作为矩形底边时,每一列往上连续有多少个 1

也就是说:

  • 如果当前列这一格是 '1',那么高度可以在上一行的基础上继续加 1

  • 如果当前列这一格是 '0',说明连续性被打断,这一列高度重置为 0

这个高度数组,就是我们把二维矩阵压缩成柱状图的桥梁。


3. 为什么更新高度时是这样写?

for (int col = 0; col < chars.length; col++) {
    if (chars[col] == '1') height[col]++;
    else height[col] = 0;
}

因为我们是逐行往下扫描的。

假设当前处理到第 r 行,那么:

  • 如果 matrix[r][col] == '1'

    • 说明这一列在当前行仍然可以和上面的连续 1 连起来

    • 所以 height[col]++

  • 如果 matrix[r][col] == '0'

    • 说明连续高度中断

    • 所以 height[col] = 0

这就是为什么它能够构造出“以当前行为底”的柱状图。


4. 为什么每一行更新完 height 后,就可以求一次最大矩形?

res = Math.max(res, largesRectangleArea(height));

因为当你固定“当前行”为矩形的底边时,height 数组就完整描述了:

每一列往上最多能延伸多高。

而任何一个只包含 1 的矩形,只要它的底边落在当前行,那么它一定可以在这个柱状图中对应成一个矩形。

所以:

  • 当前行更新出一个柱状图

  • 在这个柱状图里求最大矩形

  • 就等于求“以当前行为底的最大只含 1 的矩形”

把所有行都处理一遍,取最大值,就是整个矩阵中的答案。


5. largesRectangleArea 在做什么?

这一部分其实就是上一题“柱状图中最大的矩形”的标准写法。

它的核心思想是:

  • 对每个柱子,找到左边第一个更小的位置

  • 找到右边第一个更小的位置

  • 于是当前柱子作为最低高度时的最大宽度就确定了

  • 面积公式为:

(右边界 - 左边界 - 1) × 当前高度

这里我不再展开重复上一题的全部推导,但可以简单强调一下:

lefLower[i]

表示左边第一个比 heights[i] 更小的位置

rigLower[i]

表示右边第一个比 heights[i] 更小的位置

然后面积就是:

(rigLower[i] - lefLower[i] - 1) * heights[i]

这一步本质上就是把当前 height 当作柱状图来求解。


手动模拟一遍

我们用经典样例来走一下:

matrix =
[
  ['1','0','1','0','0'],
  ['1','0','1','1','1'],
  ['1','1','1','1','1'],
  ['1','0','0','1','0']
]

第 1 行

更新后:

height = [1,0,1,0,0]

这是一个柱状图。 它的最大矩形面积是:

1

第 2 行

更新后:

height = [2,0,2,1,1]

对应柱状图最大矩形面积是:

3

比如右边三个柱子 [2,1,1] 可以形成高为 1、宽为 3 的矩形。


第 3 行

更新后:

height = [3,1,3,2,2]

这时柱状图最大矩形面积变成:

6

例如柱子 [3,2,2] 中,以高度 2 扩展宽度 3,可以形成:

2 × 3 = 6

这也正是整个矩阵的最终答案。


第 4 行

更新后:

height = [4,0,0,3,0]

这一行对应的柱状图最大矩形面积不会超过前面的 6,所以最终答案仍然保持为 6


为什么这个方法是正确的?

因为矩阵中的任意一个全 1 矩形,都一定有一个“底边所在行”。

当我们扫描到这条底边所在行时:

  • 这个矩形覆盖到的每一列,其连续 1 的高度一定至少等于这个矩形的高度

  • 所以它一定会出现在当前构造出来的柱状图中

  • 而柱状图最大矩形算法一定不会漏掉它

换句话说:

  • 每个二维矩形,都能在某一行对应成柱状图问题中的一个矩形

  • 我们对每一行都求一次柱状图最大矩形

  • 因此最终一定能覆盖所有可能答案

这就说明这种“矩阵转柱状图”的方法是完全正确的。


复杂度分析

假设矩阵大小为:

m × n

时间复杂度

对于每一行:

  1. 更新 height 数组需要 O(n)

  2. 调用一次柱状图最大矩形也需要 O(n)

所以每一行总共是:

O(n)

一共有 m 行,因此总时间复杂度是:

O(m × n)

空间复杂度

额外使用了:

  • 一个 height 数组

  • 柱状图解法里用到的栈和左右边界数组

所以整体额外空间复杂度是:

O(n)

这道题真正想考什么?

这道题表面上是在二维矩阵里找最大矩形,但本质上考察的是两个非常重要的能力。

第一,能不能把二维问题转化成一维问题

这是整道题最精华的地方。

如果你一直停留在“二维枚举矩形”的思路里,这题会非常难。 但一旦想到“按行压缩成柱状图”,问题立刻就清晰很多。

第二,是否掌握柱状图最大矩形这个模板

这题本质上是上一题的进阶应用。 如果你已经理解了柱状图中最大矩形,那么这题真正新增的内容只剩下:

  • 怎么构造 height

  • 为什么构造出来后就能套模板

所以这两题通常都是连着刷的。


和“柱状图中最大的矩形”有什么关系?

这两题的关系非常直接。

柱状图中最大的矩形

给你一个一维高度数组,求最大矩形。

最大矩形

给你一个二维 0/1 矩阵,每一行都可以压成一个柱状图,然后对每一行调用上一题的解法。

所以这道题几乎可以看成:

柱状图最大矩形模板的二维应用版。

也正因为如此,这题非常适合在写博客时和上一题连起来讲。


总结

这道题的核心思想可以概括成一句话:

把矩阵按行扫描,并把每一行看成一个柱状图的底边,维护每列连续 1 的高度,然后对每一行调用“柱状图中最大的矩形”算法。

具体步骤就是:

  1. 开一个 height 数组,记录当前行作为底时每列连续 1 的高度

  2. 逐行扫描矩阵:

    • 如果当前位置是 1,高度加一

    • 如果是 0,高度清零

  3. 每更新完一行,就把当前 height 当作柱状图,求一次最大矩形

  4. 在所有行中取最大值

这样就能在:

O(m × n)

的时间复杂度下解决问题。

这是一道非常经典、也非常值得反复理解的题。 因为它会让你真正体会到:

  • 二维问题如何降维

  • 模板题之间是如何串联起来的

  • 单调栈为什么在矩形类题目中这么强大

把这道题真正吃透之后,再去看:

  • 接雨水

  • 柱状图中最大的矩形

  • 最大正方形

  • 岛屿类矩阵题

你会更容易意识到: 很多看起来复杂的二维题,其实关键往往是能不能找到一个合适的一维表示方式。

因为“最大矩形”本身就是二维降维思想里非常经典的一道代表题。

如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇