最近刷 LeetCode 周常题单的时候碰到了 1292 这道题,说实话第一眼看上去像是二维前缀和模板题,但真正动手写的时候发现里面藏着不少值得掰扯的细节。尤其是网上很多题解一上来就二分边长,我在本地跑了几组数据之后发现,这题根本不需要二分,直接从大到小枚举边长,第一次命中的就是答案,代码更短,思路也更直观。这篇就围绕二维前缀和怎么推、为什么这题可以不写二分、以及代码里那些容易让人翻车的边界问题,一次性讲清楚。
LeetCode 1292 的题意很直接:给一个 m x n 的矩阵 mat 和一个阈值 threshold,要我们找出最大的边长 s,使得矩阵中存在一个 s x s 的正方形,其元素和小于等于 threshold。如果不存在任何满足条件的正方形,返回 0。这题适合已经会一维前缀和、正在学二维前缀和的人,也适合想彻底搞懂“什么时候不用二分”的刷题党。看完这篇,你不仅能默写二维前缀和的代码,还能跟别人解释清楚为什么这题的二分是画蛇添足。
1. 题目拆解:先搞清楚“正方形元素和”怎么算
1.1 题意翻译成人话
矩阵里每个位置都是一个数字,我们要在这个矩阵里圈出一个正方形区域,要求这个区域里所有数字加起来不超过 threshold,然后让这个正方形的边长尽可能大。
这里有两个关键词:一个是“元素和”,一个是“正方形”。元素和不用说了,就是把一个区域里的所有数加起来。正方形意味着行数和列数相等,也就是说,如果我们枚举边长 s,那么所有可能的正方形就是所有大小为 s x s 的子矩阵。
举个例子,如果矩阵是:
text复制1 2 3
4 5 6
7 8 9
threshold 设为 15。那么边长为 2 的正方形有四个:左上角 1+2+4+5=12,右上角 2+3+5+6=16,左下角 4+5+7+8=24,右下角 5+6+8+9=28。其中左上角这个 2 x 2 正方形元素和为 12,小于等于 15,所以边长 2 是可行的。再看边长 3,整个矩阵元素和为 45,大于 15,所以答案就是 2。
这个例子虽然简单,但它说明了这道题的本质:我们要在很多个候选正方形中,判断“是否存在某个边长为 s 的正方形满足条件”,并且要找最大的那个 s。
1.2 暴力做法的时间账
如果不做任何预处理,最直接的办法就是暴力枚举所有可能的正方形。假设矩阵是 m x n,对于每个边长 s,需要枚举左上角的位置,一个有 (m-s+1) x (n-s+1) 种选择,然后对每个 s x s 的正方形重新累加一遍元素和。
这样做的复杂度是多少?枚举正方形左上角已经是 O(m x n) 级别,累加一个 s x s 的正方形又要 O(s^2),整体复杂度差不多是 O(min(m,n) x m x n x s^2),这个量级在 LeetCode 上完全不可接受。
如果矩阵是 300 x 300,光是一个 150 x 150 的正方形累加一遍就是 22500 次运算,再乘以可能的左上角数量,直接爆炸。
所以必须把“算某个正方形元素和”这个操作变成 O(1) 查询。这就是二维前缀和的用武之地。
1.3 前缀和是怎么把查询降到 O(1) 的
一维前缀和大家都熟,用一个数组 pre[i] 表示原数组前 i 个元素的和,那么任意区间 [l, r] 的和可以用 pre[r] - pre[l-1] 在 O(1) 时间内算出来。
二维前缀和就是把这个思想扩展到二维。用一个二维数组 pre[i][j] 表示从矩阵左上角 (0,0) 到 (i-1,j-1) 这个矩形区域内所有元素的和。只要 pre 数组构建好了,任意一个子矩阵的和都可以通过几个 pre 值的加减组合算出来,和子矩阵的边长无关,从而实现 O(1) 查询。
后面我会详细推导这个公式,这里先建立直觉:维数上升一维,查询方式从“两个数相减”变成“四个数加减”,但仍然是常数时间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二维前缀和:公式推导和实现细节
2.1 一维前缀和复习
我们先从一维开始,因为二维的公式就是一维的推广。
假设原数组是 a[0], a[1], ..., a[n-1],定义 pre[i] = a[0] + a[1] + ... + a[i-1],注意这里 pre 的长度是 n+1,pre[0] = 0。
那么区间 [l, r] 的和,也就是 a[l] + a[l+1] + ... + a[r],可以写成:
text复制sum(l, r) = pre[r+1] - pre[l]
为什么 pre 要开 n+1 而不是 n?因为当 l=0 时
