LeetCode 1314. 矩阵区域和
题目描述
题意分析
给定
m × n的整数矩阵mat和一个整数k,要构造同样大小的矩阵answer,其中answer[i][j]等于所有满足i-k <= r <= i+k且j-k <= c <= j+k且(r, c)落在矩阵内的mat[r][c]之和。换句话说,每个位置的答案是以它为中心、边长
2k+1的正方形窗口内所有元素的和,窗口越界的部分直接裁掉而不是补零——注意「裁掉」和「补零」在求和上结果相同,但在写下标时完全不同,前者要把边界钳位到矩阵内,后者会直接越界访问。题目的结构特征是:同一个矩阵上要做
m × n次矩形区域求和,矩阵本身自始至终不变。查询次数等于格子数,而每次查询的窗口面积可达 $(2k+1)^2$,这是典型的「大量只读区间查询」,强烈提示应该先做一次预处理,把单次查询压到常数时间。约束里
m, n可达 100、k可达 100。k允许大于矩阵边长,此时每个窗口都会覆盖整个矩阵,所有答案相同——这个极端情况必须靠钳位自然处理,不能假设i + k < m。元素取值在[1, 100],全为正数且总和最大100 × 100 × 100 = 10^6,int完全够用,不必担心溢出。边界要留意四点:
k可能为 0,此时answer就是mat本身;窗口的四个边都可能越界,上下左右要分别钳位;答案矩阵与输入同形,不是(m+1) × (n+1);窗口是闭区间,两端都要取到。
解法:二维前缀和
核心思路
每个答案都是原矩阵上的矩形区域和,而且矩阵在查询期间不变。若逐格扫描窗口,相邻窗口会重复计算大量元素;二维前缀和可以把每次矩形求和降为 $O(1)$。
定义
prefix[i][j]为原矩阵前i行、前j列的元素和。第 0 行和第 0 列表示空区域,均为 0。递推使用容斥:
prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1] + mat[i-1][j-1]。构造不变量:计算
prefix[i][j]时,它依赖的上方、左侧和左上三格都已经完成,所以该格始终准确表示对应左上矩形之和。对原矩阵闭区间
[row1, row2] × [col1, col2],区域和为:
prefix[row2+1][col2+1] - prefix[row1][col2+1] - prefix[row2+1][col1] + prefix[row1][col1]。两个减项移除窗口上方和左侧区域,左上重叠部分被减了两次,所以最后加回一次。每个中心
(i, j)的四条边分别钳位到矩阵范围,再代入同一公式。正确性:前缀递推由容斥准确覆盖每个左上矩形;查询公式又从右下大矩形中精确保留目标区域。因此每个
answer[i][j]都等于题目规定的裁剪窗口和。
解题步骤
- 创建
(rows + 1) × (cols + 1)的prefix,按行列递增构造二维前缀和。- 对每个位置
(i, j),计算并钳位row1、row2、col1、col2。- 用四项容斥公式求窗口和并写入同尺寸结果矩阵。
对
mat = [[1,2,3],[4,5,6],[7,8,9]]、k = 1,中心(1,1)的窗口覆盖整个矩阵,结果为 45;右下角(2,2)的窗口为[[5,6],[8,9]],结果为 28。
k = 0时四条边都等于当前位置,结果就是原矩阵的值;k大于行列数时,钳位后每个窗口都覆盖整个矩阵;非方阵必须分别使用rows和cols处理上下界。
代码实现
class Solution {
public int[][] matrixBlockSum(int[][] mat, int k) {
int rows = mat.length;
int cols = mat[0].length;
int[][] prefix = new int[rows + 1][cols + 1];
for (int i = 1; i <= rows; i++) {
for (int j = 1; j <= cols; j++) {
prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1]
- prefix[i - 1][j - 1] + mat[i - 1][j - 1];
}
}
int[][] answer = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
int row1 = Math.max(0, i - k);
int row2 = Math.min(rows - 1, i + k);
int col1 = Math.max(0, j - k);
int col2 = Math.min(cols - 1, j + k);
answer[i][j] = prefix[row2 + 1][col2 + 1]
- prefix[row1][col2 + 1]
- prefix[row2 + 1][col1]
+ prefix[row1][col1];
}
}
return answer;
}
}
func matrixBlockSum(mat [][]int, k int) [][]int {
rows, cols := len(mat), len(mat[0])
prefix := make([][]int, rows+1)
for i := range prefix {
prefix[i] = make([]int, cols+1)
}
for i := 1; i <= rows; i++ {
for j := 1; j <= cols; j++ {
prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1] + mat[i-1][j-1]
}
}
answer := make([][]int, rows)
for i := 0; i < rows; i++ {
answer[i] = make([]int, cols)
for j := 0; j < cols; j++ {
row1, row2 := maxInt(0, i-k), minInt(rows-1, i+k)
col1, col2 := maxInt(0, j-k), minInt(cols-1, j+k)
answer[i][j] = prefix[row2+1][col2+1] - prefix[row1][col2+1] - prefix[row2+1][col1] + prefix[row1][col1]
}
}
return answer
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
复杂度分析
- 时间复杂度:$O(mn)$。构造前缀和与生成答案各遍历矩阵一次,每个区域查询为 $O(1)$。
- 空间复杂度:$O(mn)$,用于二维前缀和;结果矩阵不计入额外空间。
关键点总结
- 静态矩阵上的大量区域求和,优先考虑二维前缀和。
- 第 0 行、第 0 列哨兵统一了构造与查询边界。
- 矩形查询必须“减上、减左、加回左上重叠”。
- 窗口四条边要独立钳位,尤其不能把行数与列数混用。
易错点总结
- 漏掉左上补偿项:内部窗口会把左上区域多减一次,例如样例右下窗口会得到 27 而不是 28。
- 右下坐标忘记加 1:
prefix下标表示行列数量,闭区间右下角必须转换为row2 + 1、col2 + 1。- 只钳位部分边界:
k可以大于矩阵尺寸,四条边都可能越界。- 行列上界写反:非方阵上应分别使用
rows - 1与cols - 1。- 把前缀数组开成原矩阵大小:会失去空行空列哨兵并引入大量边界分支。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 303. 区域和检索 - 数组不可变 | 简单 | 一维版模板,先在这里把「下标是计数」的偏移想透 |
| 304. 二维区域和检索 - 矩阵不可变 | 中等 | 同款容斥,区别只是查询区间由调用方给定而非由中心点推出 |
| 308. 二维区域和检索 - 矩阵可修改 | 中等 | 矩阵可改导致前缀失效,需要二维树状数组同时支持更新与查询 |
| 1292. 元素和小于等于阈值的正方形的最大边长 | 中等 | 在前缀和之上再套一层对边长的二分,利用「边长越大和越大」的单调性 |
| 1074. 元素和为目标值的子矩阵数量 | 困难 | 枚举上下行界压成一维,再用哈希表做前缀和配对计数 |
| 363. 矩形区域不超过 K 的最大数值和 | 困难 | 压成一维后要在有序集合里找最小的不小于某值的前缀,需要平衡树而非哈希 |
| 221. 最大正方形 | 中等 | 用动态规划递推边长比前缀和更直接,状态是「以该格为右下角的最大边长」 |
| 1277. 统计全为 1 的正方形子矩阵 | 中等 | 与 221 同一递推式,但把最大值改成累加,体现「边长即方案数」 |
| 85. 最大矩形 | 困难 | 逐行压成柱状图后套单调栈,说明矩形类问题不是只有前缀和一条路 |
| 面试题 17.24. 最大子矩阵 | 困难 | 枚举行界 + 一维最大子段和,还要额外记录四个边界坐标 |
| 1109. 航班预订统计 | 中等 | 差分数组是前缀和的逆过程,适合「区间批量加、最后统一查」 |
| 370. 区间加法 | 中等 | 一维差分模板,与本题构成「先改后查」与「只查不改」的对照 |
| LCR 013. 二维区域和检索 - 矩阵不可变 | 中等 | 与 304 同题,适合把容斥公式再默写一遍 |