1. 题目拆解:先分清统计对象与评判标准
1.1 从文字描述到数学条件
2026年3月19日的LeetCode每日一题是第3212题:统计X和Y频数相等的子矩阵数量。题目背景很简单,给定一个二维矩阵,矩阵中每个格子可能是字符'X'或者'Y',要求统计出所有满足"X的数量等于Y的数量"的子矩阵个数。这里的关键是"子矩阵"的边界定义:子矩阵必须是原矩阵中连续行、连续列围成的矩形区域,不能跳行跳列取。
这类题第一次见到容易懵,因为"统计数量"这个动作本身不难,难在数据规模上。LeetCode平台给的约束一般是矩阵行列数在几十到几百之间,如果直接用四重循环枚举左上角和右下角,再遍历内部所有元素做统计,计算量会迅速膨胀到无法接受的程度。我见过不少同学第一次提交暴力版本之后,看到超时提示才意识到问题没这么简单。
这里值得先聊一个很核心的直觉:题面里说的是"X的频数等于Y的频数",翻译成数学语言其实是"X的个数减去Y的个数等于0"。因为子矩阵里只有X和Y两类有效字符,我们完全可以给X赋值为+1,给Y赋值为-1,那么"两者数量相等"就等价于"整个子矩阵的元素和等于0"。这个转换看起来只是换了一种记录方式,但它把字符串比较问题变成了数值统计问题,后续所有高效算法都建立在这一个看似不起眼的等价变换上。
1.2 为什么"X和Y相等"能转化为"和为零"
用一个具体的小例子来感受一下。假设有一个2行2列的矩阵:
X Y
X X
如果按原样数,左上角那个1x1子矩阵是单个'X',X频数是1、Y频数是0,不相等。但如果我们把X看作1、Y看作-1,这个子矩阵的元素和就是1,不是0,结论一致。再看整个2x2矩阵,X有3个、Y有1个,不相等,对应元素和是1+(-1)+1+1=2,也不是0。
那么什么样的子矩阵会被计入统计?比如在一行一列这种极端情况下,一个只有X和Y各一个的1x2子矩阵,X频数1、Y频数1,相等,映射成数值就是1+(-1)=0,和为0,计入统计。所以"X频数等于Y频数"和"子矩阵元素和为0"是同一件事的两种说法。
这个等价变换的价值在于:数值和可以前缀和预处理,而字符频数对比不能直接前缀和。只要预处理出二维前缀和矩阵,任何矩形区域的元素和都能在O(1)时间内算出来,不需要再逐个格子数X和Y。当然,前缀和只是第一步,因为即便单次查询是O(1),要枚举所有子矩阵本身也有O(m²n²)种可能,这个数量级依然很大。这个问题留下的核心矛盾是:如何在不枚举所有子矩阵的前提下,借助"和为0"这一条件快速统计数量。后面几节会一步步把这个矛盾拆开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力到二维前缀和的演化:性能瓶颈在哪里
2.1 第一版暴力:枚举所有矩形
先来看看最简单的暴力思路,这有助于理解后面优化到底优化了什么。暴力方案分为两层:第一层用四个变量i1、j1、i2、j2分别表示子矩阵左上角和右下角的坐标,第二层遍历这个矩形中的所有格子统计X和Y的数量,最后比较是否相等。如果矩阵尺寸是m行n列,左上角坐标有m×n种可能,右下角坐标在左上角确定后也有O(mn)种可能,所以枚举矩形本身是O(m²n²)的。每个矩形内部再遍历一次,复杂度再乘一个O(mn),总复杂度高达O(m³n³)量级,这还没算比较频数的常数操作。
如果m和n都等于100,100³×100³这个数量级简直是天文数字,现代计算机也不可能在几秒内跑完。即使把内部遍历换成二维前缀和,把单次矩形求和降到O(1),枚举所有矩形的O(m²n²)依然让人头疼。m=200、n=200时,m²n² = 16亿,16亿次O(1)查询在C++里也许勉强能卡线,在Python里几乎不可能通过。
这里也解释了为什么很多人第一版代码写完,自己本地试了3x3、4x4的小矩阵没问题,一提交就超时。不是代码写错了,而是算法复杂度选错了。刷题和业务开发的一个共同点是:先估算数据规模,再决定技术方案,而不是上来就写循环。
2.2 二维前缀和的真正瓶颈:枚举次数而非计算成本
先花一点篇幅把二维前缀和的思路整理清楚,因为后面优化的起点就是它。设矩阵M的行索引从0到m-1,列索引从0到n-1。定义前缀和数组pre[i][j]表示从(0,0)到(i,j)这个矩形区域内所有元素的和。递推公式是pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + M[i][j],这个公式的本质是"左上角矩形加右上角矩形减重复的左上角子矩形,再加当前格子"。有了pre之后,任意子矩阵(i1,j1)到(i2,j2)的和就等
