大家好,我是你的CSDN技术博主,今天是2026年1月15日,继续每日一题!
今天分享一道非常经典的中等偏难 动态规划题目------1277. 统计全为 1 的正方形子矩阵(Count Square Submatrices with All Ones)。
这道题是二维 DP 的代表作,思路清晰但容易写错边界,刷完后对"最大正方形""子矩阵计数"类问题会有质的提升!
一、题目理解
题目描述
给你一个 m × n 的二进制矩阵 mat,每个元素要么是 0,要么是 1。
请你统计并返回矩阵中 全部由 1 组成的正方形子矩阵 的数量。
注意:
- 边长为 1 的 1×1 正方形也算
- 正方形必须是连续的 1 区域
示例解析
示例 1:
输入:mat = [
[0,1,1,1],
[1,1,1,1],
[0,1,1,1]
]
输出:15
解释:
- 1×1 的正方形:共 8 个
- 2×2 的正方形:共 6 个
- 3×3 的正方形:共 1 个
总共 8 + 6 + 1 = 15
示例 2:
输入:mat = [
[1,0,1],
[1,1,0],
[1,1,0]
]
输出:7
约束条件
- 1 ≤ m, n ≤ 300
- mat[i][j] 是 0 或 1
m,n ≤ 300,O(mn) 时间可接受,DP 空间也可优化到 O(n)。
二、解题思路(DP + 状态转移)
核心思想
定义 dp[i][j] 表示:以 (i,j) 为右下角的最大全 1 正方形边长
如果 mat[i][j] == 0,则 dp[i][j] = 0
如果 mat[i][j] == 1,则:
dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
意思是:以 (i,j) 为右下角的最大正方形边长,取决于它左、上、左上三个方向的最大正方形,再加 1。
为什么这样转移?
假设左上角三个位置的最大正方形边长都是 k,那么把 (i,j) 这个 1 补上,就能形成一个 (k+1)×(k+1) 的正方形。
如何统计总数?
- 遍历过程中,每当计算出一个 dp[i][j] = k
- 说明以 (i,j) 为右下角,有 k 个大小不同的正方形(边长 1 到 k)
- 所以答案直接累加 dp[i][j] 的值即可!
三、代码演示
Python3 代码(空间优化版)
python
from typing import List
class Solution:
def countSquares(self, matrix: List[List[int]]) -> int:
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
dp = [0] * (n + 1) # 只用一行滚动数组
ans = 0
for i in range(m):
prev = 0 # 上一行的左上角值(用于对角线)
for j in range(n):
temp = dp[j + 1] # 保存旧值(上一行的同一列)
if matrix[i][j] == 1:
dp[j + 1] = min(prev, dp[j], temp) + 1
ans += dp[j + 1]
else:
dp[j + 1] = 0
prev = temp # 更新对角线值
return ans
Java 代码(标准二维 DP)
java
class Solution {
public int countSquares(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int m = matrix.length;
int n = matrix[0].length;
int[][] dp = new int[m + 1][n + 1];
int ans = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (matrix[i-1][j-1] == 1) {
dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
ans += dp[i][j];
}
// else dp[i][j] = 0; 默认就是0
}
}
return ans;
}
}
四、代码解读
-
dp[i][j]:以 matrix[i-1][j-1] 为右下角的最大正方形边长
-
状态转移 :
if (matrix[i-1][j-1] == 1) dp[i][j] = min(左、上、左上) + 1 else dp[i][j] = 0 -
统计答案:每次计算 dp[i][j] 时,累加它(因为边长 k 代表有 k 个正方形以该点为右下角)
五、复杂度分析
- 时间复杂度:O(m × n)
- 空间复杂度 :
- 二维版:O(m × n)
- 滚动数组版:O(n)(本题只用一行)
六、总结
这道题是二维 DP + 计数类的经典题目。核心公式:
dp[i][j] = min(左、上、左上) + 1 (如果当前点是1)
答案 = sum(dp[i][j] for all i,j)
掌握这个模板,就能轻松解决:
- LeetCode 221. 最大正方形(求最大边长)
- LeetCode 1277. 本题(求总数)
- LeetCode 1504. 统计全 1 子矩形(稍作变形)
如果本文对你有帮助,欢迎点赞、收藏、关注~
明天每日一题再见!