221. 最大正方形(Maximal Square)

频次 ★★★★ · 难度 🟡 · 高频:字节/美团

题目

m×n 二进制矩阵(‘0’ 和 ‘1’),找到只包含 ‘1’ 的最大正方形,返回其面积。

示例

输入:
[["1","0","1","0","0"],
 ["1","0","1","1","1"],
 ["1","1","1","1","1"],
 ["1","0","0","1","0"]]
输出: 4  (右下角 2×2 正方形)

思路

二维 DPdp[i][j] 表示以 (i,j) 为右下角的最大全 ‘1’ 正方形边长。

转移方程:dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1,当 matrix[i][j] == '1' 时。

直观理解:要形成一个更大的正方形,左上、上方、左方三个邻居都得有足够的边长。

空间优化:滚动数组,只保留上一行即可降到 O(n)。

代码

public int maximalSquare(char[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
        return 0;
    int m = matrix.length, n = matrix[0].length;
    int[] dp = new int[n + 1];         // 空间优化:一维滚动
    int max = 0, prev = 0;             // prev 记录 dp[j] 的旧值(即 dp[i-1][j-1])
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            int temp = dp[j + 1];      // 保存旧值给下一轮作为左上角
            if (matrix[i][j] == '1') {
                dp[j + 1] = Math.min(Math.min(dp[j + 1], dp[j]), prev) + 1;
                max = Math.max(max, dp[j + 1]);
            } else {
                dp[j + 1] = 0;
            }
            prev = temp;
        }
    }
    return max * max;
}

复杂度

  • 时间:O(m × n) —— 遍历每个元素一次
  • 空间:O(n) —— 滚动数组

边界条件

  • 空矩阵:返回 0
  • 单行或单列:若有 ‘1’ 返回 1,否则 0
  • 全 ‘0’ 矩阵:dp 全为 0,max = 0,返回 0

变式

易错点

  • char 类型:matrix[i][j] 是 ‘1’ 或 ‘0’,不是整数,比较时用字符常量
  • dp[j+1] 在更新前保存的 temp 作为下一轮的左上角(dp[i-1][j-1]),注意滚动数组的错位索引
  • 返回 max * max 而非 max——题目要求面积(边长的平方)

面试追问

  • DP 方程”取 min 三个邻居”的直觉理解? 以 (i,j) 为右下角的大正方形由三个方向的小正方形决定:上方柱子高度、左边宽度、左上方支撑——三者最短的那个就是新正方形的边长。画个 2×2 全 1 矩阵验证:dp[1][1] = min(1,1,1)+1 = 2
  • 能不能用其他方法? 可以:对每个 ‘1’ 做 BFS 扩展(O(m²n²)),或用「每列连续 1 的高度 + 单调栈」求最大矩形(后者可改造成求正方形但不如 DP 直观)
  • 如果矩阵很大(10⁴×10⁴)又很稀疏,有什么优化? 可以用行/列的计数跳过全零区域,或用稀疏矩阵存储,但 DP 本身的 O(mn) 已经最优(至少要读一遍数据)

关联题