LeetCode 1292 这道题,我觉得是二维前缀和的教科书级例题。题面很短:给你一个 m x n 的矩阵,找一个正方形区域,让元素和小于等于给定的阈值 threshold,返回能满足条件的最大边长。乍一看,很多人第一反应是二分答案,然后写一个 check 函数去验证某个边长是否可行。但仔细看数据范围,m、n 最大只有 300,压根不用二分,直接用二维前缀和预处理,再暴力枚举边长就能稳稳过掉,而且代码写起来反而更直白、更不容易错。这篇就用最通俗的方式,把二维前缀和怎么推、代码怎么写、坑在哪里讲清楚。
1. 题目拆解:它到底在求什么
1.1 数据规模和暴力解法的瓶颈
先看数据范围:矩阵的长和宽都在 300 以内,元素值是非负整数,阈值 threshold 最大到 1e9。如果完全按暴力思路,枚举每个可能的正方形左上角,再枚举边长,然后重新累加这个正方形内部的所有元素,那复杂度会是多少?
假设矩阵是 300 x 300,正方形边长从 1 到 300。每次累加一个 k x k 的正方形,需要 k^2 次加法。把所有边长、所有左上角的情况都枚举一遍,总操作量大概是:
S = Σ_{k=1}^{300} (301 - k)^2 * k^2
这个数非常大,粗算一下:当 k 在中间段(比如 k=150)时,(301-k)^2 约等于 151^2 ≈ 22801,k^2 ≈ 22500,相乘再乘上左上角数量,单层循环就接近 5 亿次加法。整体加起来,C++ 在 LeetCode 上大概率超时,Python 更是想都别想。
所以核心矛盾是:我们需要一种方法,能在 O(1) 时间内算出任意一个矩形区域的和,而不是每次现加。这正是二维前缀和存在的意义。
1.2 从一维前缀和到二维的思维迁移
一维前缀和大家都熟:给一个数组 nums,预处理一个 prefix 数组,prefix[i] 表示 nums[0] 到 nums[i-1] 的和,这样任意区间 [l, r] 的和就是 prefix[r+1] - prefix[l]。它把「区间求和」从 O(n) 降到了 O(1)。
二维前缀和的思路完全一样,只是把「区间」换成「矩形」。我们需要一个二维数组 p,让 p[i][j] 表示从矩阵左上角 (0,0) 到 (i-1,j-1) 这个矩形区域内所有元素的和。注意这里故意用 1-based 的含义来描述,代码里通常也把 p 开成 (m+1) x (n+1),就是为了让边界处理更干净,这个细节后面专门讲。
一旦有了 p,任何以 (x1,y1) 为左上角、(x2,y2) 为右下角的矩形和,都可以通过 p 数组的四项组合算出来。整个问题的难点就全集中在这两步:如何快速构造 p,以及如何准确写出矩形和的查询公式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二维前缀和的核心细节:预处理、查询与公式推导
2.1 预处理:容斥公式怎么来的
p[i][j] 的定义是「从 (0,0) 到 (i-1,j-1) 的矩形和」。怎么递推?我们盯住右下角这个格子,也就是矩阵中的第 i-1 行、第 j-1 列,它的值记为 mat[i-1][j-1]。
p[i][j] 其实可以拆成三块:
- 上面部分:p[i-1][j],它覆盖了从 (0,0) 到 (i-2,j-1) 的矩形;
- 左边部分:p[i][j-1],它覆盖了从 (0,0) 到 (i-1,j-2) 的矩形;
- 当前格子:mat[i-1][j-1]。
但把 p[i-1][j] 和 p[i][j-1] 直接相加,左上角那块区域 p[i-1][j-1] 被重复加了一次。因为 p[i-1][j] 覆盖了 (0,0) 到 (i-2,j-1),p[i][j-1] 覆盖
