LeetCode 1314. 矩阵区域和
题目描述

题意分析
对矩阵每个位置
(i, j),求行号位于[i-k, i+k]且列号位于[j-k, j+k]的所有合法格子之和。行、列距离分别受限,区域是矩形,包含对角方向的格子。超出矩阵的部分不参与求和,因此靠边区域会被裁小,矩阵也不一定为正方形。返回与输入同尺寸的矩阵,各答案都依据原矩阵计算,不能把已算出的区域和当成后续输入。
解法:二维前缀和
核心思路
[!blue]
不同中心的区域大量重叠,逐区间相加会重复读取许多格子。用二维前缀和保存公共部分:
prefix[i][j]表示前i行、前j列的矩形总和,前缀表比原矩阵多一行一列,零行、零列代表空区域。构造一个前缀时,先加上方前缀和左侧前缀,两者重复计算了左上角的交集,所以减去左上前缀,再加当前原格子。按行列递增填表时,这三个依赖都已经计算完。
每次查询先把上下左右限制到矩阵边界,得到闭区间
[r1, r2] × [c1, c2]。从覆盖右下角的大前缀prefix[r2 + 1][c2 + 1]出发,减去目标上方部分prefix[r1][c2 + 1],再减去左侧部分prefix[r2 + 1][c1];左上交集被减了两次,最后加回prefix[r1][c1]。这四项恰好让目标矩形保留一次、外部格子抵消为零。前缀下标按行列数量定义,闭区间的右下端要加一;额外零行零列让贴着上边或左边的查询也能使用同一个公式。
每个中心只需计算裁剪边界和读取四项前缀,得到答案后写入新矩阵。即使
k很大使多个区域覆盖整个矩阵,也无需重复扫描它们的内部。
解题步骤
- 建立
(rows + 1) × (cols + 1)的前缀表,首行首列保持零。- 逐格按“上方加左侧、减左上重叠、加原元素”填表。
- 对每个中心,用行数裁剪上下边界,用列数裁剪左右边界。
- 按四项容斥公式计算矩形和,写入同尺寸结果矩阵的当前位置。
- 全部位置处理后返回结果。
代码实现
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(mn)$,结果矩阵另占同阶空间。
关键点总结
[!green]
- 前缀坐标是行列数量,闭区间右下角需要加 1。
- 减去上方和左侧后,左上重叠区域要加回一次。
- 行数与列数分别用于各自边界,不能混用。
- 只读取原矩阵,结果写入新矩阵。
易错点总结
[!yellow]
- 漏掉左上补偿:内部窗口会被多减一块。
- 没有截断越界边界:大 k 或靠边中心可能访问非法下标。
- 把答案也开成多一行多一列:哨兵只属于前缀表,返回尺寸仍是原矩阵尺寸。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 304. 二维区域和检索 - 矩阵不可变 | 中等 | 二维前缀和可常数时间查询每个裁剪后的邻域矩形和。 |
| 661. 图片平滑器 | 简单 | 原题固定3×3窗口并求平均,本题窗口半径可变且只求和。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!