https://blog.csdn.net/2601_95366422/article/details/158848006
上节课链接
一.题目

二.思路
2.1 审题
题目要求对于矩阵中的每一个位置 (r, c) ,计算以它为中心、向四周各扩展 k 个单位得到的正方形区域内的所有元素之和。也就是说,行范围是从 r - k 到 r + k ,列范围是从 c - k 到 c + k 。

2.2 思路讲解
-
下标偏移问题 :原始矩阵的下标是从 0 开始的,而我们在构建二维前缀和数组时,需要让下标从 1 开始,这样可以避免边界判断(因为 dp[0][*] 和 dp[*][0] 可以初始化为 0)。因此,我们创建一个大小为 (m+1) × (n+1) 的前缀和数组 dp ,其中 dp[i][j] 表示原矩阵中从 (0,0) 到 (i-1, j-1) 这个子矩阵的和。这样,原矩阵中位置 (r, c) 的元素对应到 dp 中的下标是 (r+1, c+1)。
-
前缀和计算公式 :构建 dp 时,使用二维前缀和递推公式:
dp[i+1][j+1] = dp[i+1][j] + dp[i][j+1] - dp[i][j] + mat[i][j]。这里 i 和 j 遍历原矩阵的行和列(从 0 到 m-1, 0 到 n-1),这样计算出的 dp[i+1][j+1] 正好对应原矩阵从 (0,0) 到 (i,j) 的和。

-
查询子矩阵和 :对于每个位置 (i, j) ,我们需要计算以它为中心、边长为 2k+1 的正方形区域的和。但由于边界可能越界,实际范围是:
行范围:r1 = max(0, i - k) ,r2 = min(m - 1, i + k) ,
列范围:c1 = max(0, j - k) ,c2 = min(n - 1, j + k) 。
为了利用前缀和数组,我们将这些边界转换为 dp 中的下标:
x1 = r1 + 1 ,y1 = c1 + 1 ,x2 = r2 + 1 ,y2 = c2 + 1 。那么对应的子矩阵和可以通过前缀和快速得到:
sum = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]

三.代码演示
cpp
class Solution {
public:
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k)
{
int m = mat.size();
int n = mat[0].size();
vector<vector<int>> dp(m + 1,vector<int>(n + 1));
vector<vector<int>> answer(m,vector<int>(n));
//求二维前缀和
for(int i = 0;i < m;i++)
{
for(int j = 0;j < n;j++)
{
dp[i + 1][j + 1] = dp[i + 1][j] + dp[i][j + 1] - dp[i][j] + mat[i][j];
}
}
//使用二维前缀和
for(int i = 0;i < m;i++)
{
for(int j = 0; j < n;j++)
{
int x1 = max(0,i - k) + 1,y1 = max(0,j - k) + 1;
int x2 = min(m - 1,i + k) + 1,y2 = min(n - 1,j + k) + 1;
answer[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
}
}
return answer;
}
};
四.代码讲解
一、初始化前缀和数组
为了高效计算任意子矩阵的和,我们首先构建一个二维前缀和数组 dp。由于原矩阵下标从 0 开始,而前缀和公式通常需要下标从 1 开始以简化边界处理,因此我们创建一个大小为 (m+1) × (n+1) 的数组 dp,其中 m 和 n 分别为原矩阵的行数和列数。dp[i][j] 表示原矩阵中从左上角 (0,0) 到 (i-1, j-1) 这个矩形区域的所有元素之和。这样,dp[0][*] 和 dp[*][0] 自动为 0,为后续计算提供了安全保障。
二、构建二维前缀和
我们通过两层循环遍历原矩阵的每一个元素 mat[i][j](i 从 0 到 m-1,j 从 0 到 n-1),并按照二维前缀和递推公式 更新 dp:
dp[i+1][j+1] = dp[i+1][j] + dp[i][j+1] - dp[i][j] + mat[i][j]。 这个公式的几何意义是:当前位置(i+1, j+1)对应的矩形和等于左边矩形 (dp[i+1][j])加上右边矩形 (dp[i][j+1]),再减去重复计算的左上角矩形 (dp[i][j]),最后加上当前元素mat[i][j]。通过一次双重循环,我们就能得到所有前缀和,时间复杂度 O(m×n)。
三、查询每个位置的区域和
题目要求对于每个位置 (i, j),计算以它为中心、向四周扩展 k 个单位得到的正方形区域的和。由于可能越界,我们需要先确定实际的行列范围:
-
行范围:
r1 = max(0, i - k),r2 = min(m - 1, i + k) -
列范围:
c1 = max(0, j - k),c2 = min(n - 1, j + k)
为了使用前缀和数组,将这些边界转换为 dp 中的下标(因为 dp 的下标比原矩阵大 1):
-
x1 = r1 + 1,y1 = c1 + 1 -
x2 = r2 + 1,y2 = c2 + 1那么子矩阵的和可以通过容斥原理快速得到: -
sum = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]这里,d**p[x2][y2]**是从左上角到(r2, c2)的大矩形和 ,减去左边矩形dp[x2][y1-1]和右边矩形dp[x1-1][y2]和,再加回被重复减去的左上角矩形dp[x1-1][y1-1]。