最近刷LeetCode刷到第1895题“最大的幻方”,光看题目名字差点被“幻方”两个字唬住,以为是什么数学竞赛题。实际拆开之后发现,核心考的就是两件事:一是能不能看懂“幻方”的定义,二是懂不懂用前缀和去加速矩阵子区域的求和判断。题目本身是中等难度,但背后的思维链非常典型,从暴力枚举到前缀和优化,再到枚举顺序的剪枝,一套组合拳下来,很多类似矩阵题都能触类旁通。
这道题适合什么人刷?如果你在准备面试,或者刚把一维前缀和弄明白、想看看怎么平移到二维矩阵里,这题是很好的过渡练习。它不会像二维差分那么绕,但也足够让你体会“把O(k^2)的求和变成O(1)查表”到底是怎么一回事。文章里我会把暴力思路、前缀和预处理的完整过程、check函数怎么写、复杂度到底该怎么算,以及我实际提交时踩过的几个坑全部摊开讲,文末附完整可运行的Python和C++代码。
1. 题目到底在问什么:先把“幻方”翻译成代码条件
1.1 “幻方”的数学条件拆解
题目给了你一个m行n列的整数矩阵grid,让你在这个矩阵里找一个边长最大的正方形子矩阵,要求这个子矩阵满足:每一行的和、每一列的和、两条对角线的和,全部相等。
这里有个关键点要明确:不是要求整个矩阵满足幻方性质,而是要求从中截取出来的某个正方形满足。也就是说,我们要做的事情是“在矩阵里搜索一个符合条件的正方形”。
我把这个条件用白话再翻译一遍:假设你找到了一个左上角在(i, j)、边长是k的正方形,它的行和、列和、主对角线和副对角线都必须等于同一个数target。这个target不是预先给定的,而是由这个正方形本身的第一行元素和决定的。
判断条件四件套拆开就是:
- k行,每行的和都是target
- k列,每列的和都是target
- 主对角线(从左上到右下)的和是target
- 副对角线(从右上到左下)的和是target
任何一个不满足,这个正方形就不算幻方。
1.2 边界情况:k = 1 一定是答案兜底
这里有一个很容易被忽略的边界:当k = 1时,正方形只有一个元素grid[i][j]。这一行(就是它自己)、这一列(就是它自己)、两条对角线(也都是它自己)的和当然全都相等。所以任何1×1的子矩阵天然就是幻方。
这意味着答案至少是1,永远不会出现“找不到幻方”的情况。这个边界在后面写代码的时候特别重要,你甚至可以在主循环里直接return 1兜底,保证函数一定有返回值。
1.3 先想清楚“最大”这两个字怎么处理
“最大”意味着我们要找最大的边长k。最直观的办法当然是枚举所有可能的k,然后对每个k检查是否存在至少一个边长k的幻方子矩阵。但是枚举顺序有讲究:从大到小枚举,一旦发现某个k可行,直接返回,不用继续看更小的k。
这个剪枝看起来不起眼,实际上非常关键。如果从小到大枚举,你必须找到最后一个可行的k才能返回;而从大到小枚举,你找到的第一个可行k就是答案,后面的所有计算都可以省掉。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法与复杂度分析:为什么不能直接硬算
2.1 暴力方案:三重循环套四层求和
最简单的思路,就是枚举所有可能的左上角(i, j)和边长k,然后老老实实把子矩阵里所有元素求和、比较。
伪代码大概是:
- 外层循环k,从min(m, n)往下递减
- 中层循环枚举左上角i
- 内层循环枚举左上角j
- 对每个正方形,用四层循环分别计算行和、列和、两条对角线和,一旦发现不相等就跳过
这种做法的正确性毋庸置疑,但问题在于慢。对每个候选正方形,行求和要算k行,每行k个元素;列求和要算k列,每列k个元素;两条对角线加起来还要算2k个元素。光一个check函数,时间复杂度就至少是O(k^2),因为光是“取出所有元素看一眼”就要k^2次操作。
2.2 复杂度到底有多高:用N = 50算给你看
LeetCode这题的约束是m和n都不超过50。别看50不大,我们把复杂度算清楚。
假设m = n = N,枚举一个边长k时,左上角可选的坐标有(N - k + 1)^2个。对每个坐标做一次check,check的复杂度是O(k^2)。所以总操作量大约是:
sum_{k=1}^{N} (N - k + 1)^2 * k^2
当N = 50时,这个值大约是百万量级,暴力其实也能勉强通过。但问题在于,暴力写法里每算一个行和就要重新做k次加法,代码跑起来常数很大,而且如果题目数据范围稍微放大到N = 200,这个式子就会变成亿级甚至十亿级,直接超时。
所以这道题真正的目的不是让你靠暴力过用例,而是逼你想明白:能不能把check函数里的“求和”从每次重新加优化成“查表一次得到”?这就是前缀和的用武之地。
2.3 从暴力到优化的思维转折
我们要优化的核心是:在check一个k×k正方形时,要判断k行和k列的和是否等于target。如果能在O(1)时间内算出任意一行从第y列到第y+k-1列的和,以及任意一列从第x行到第x+k-1行的和,那么整个check的复杂度就可以从O(k^2)降到O(k)。
为什么能降?因为行和列分别只需
