每日一题#21 二维 DP + 计数类

大家好,我是你的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 子矩形(稍作变形)

如果本文对你有帮助,欢迎点赞、收藏、关注~

明天每日一题再见!

相关推荐
朔北之忘 Clancy2 小时前
第一章 顺序结构程序设计(3)
c++·算法·青少年编程·竞赛·教材·考级·讲义
虫小宝2 小时前
企业微信官方API与自建机器人系统的鉴权体系对比及Java集成方案
java·机器人·企业微信
飞Link2 小时前
微调阶段中的模型自我提升、通用模型蒸馏和数据扩充
人工智能·算法·数据挖掘
70asunflower2 小时前
Jupyter Notebook 详细快捷键操作指南
ide·python·jupyter
共享家95272 小时前
力扣刷题之路
算法·leetcode·深度优先
运维行者_2 小时前
Applications Manager 引入持续剖析技术,突破传统 APM 监控瓶颈
java·运维·网络·jvm·数据库·安全·web安全
biyezuopinvip2 小时前
基于JavaSSM+MySQL的机房预约管理系统设计与实现
java·mysql·毕业设计·论文·ssm·jsp·机房预约管理系统设计与实现
开开心心_Every2 小时前
免费视频画质增强:智能超分辨率无损放大
java·服务器·前端·python·学习·edge·powerpoint
FJW0208142 小时前
Python面向对象三大特征封装,继承,多态
开发语言·python