题目描述
给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。

上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。
示例:

提示:
你可以假设矩阵不可变。
会多次调用 sumRegion 方法。
你可以假设 row1 ≤ row2 且 col1 ≤ col2 。
前言
这道题是「303. 区域和检索 - 数组不可变」的进阶,第 303 题是在一维数组中做区域和检索,这道题是在二维矩阵中做区域和检索。
这道题有两种解法,分别是对每一行计算一维前缀和,以及对整个矩阵计算二维前缀和。
方法一:一维前缀和
关键词:每一行计算一维前缀和
第 303 题中,初始化时对数组计算前缀和,每次检索即可在 O(1) 的时间内得到结果。可以将第 303 题的做法应用于这道题,初始化时对矩阵的每一行计算前缀和,检索时对二维区域中的每一行计算子数组和,然后对每一行的子数组和计算总和。
具体实现方面,创建 m 行n+1 列的二维数组 sums,其中 m 和 n 分别是矩阵matrix 的行数和列数,sums[i] 为 matrix[i] 的前缀和数组。将sums 的列数设为 n+1 的目的是为了方便计算每一行的子数组和,不需要对 col1=0 的情况特殊处理。
C++
class NumMatrix
{
public:
vector<vector<int>> sums;
NumMatrix(vector<vector<int>>& matrix)
{
int m = matrix.size();
if (m > 0)
{
int n = matrix[0].size();
sums.resize(m, vector<int>(n + 1));//注意二维vector的resize的用法
for (int i = 0; i < m; i++)
{
sums[i][0] = 0;
for (int j = 0; j < n; j++)//注意这里是j<n,因为sums[i][j + 1]已经加过1了。
{
sums[i][j + 1] = sums[i][j] + matrix[i][j];
}
}
}
}
int sumRegion(int row1, int col1, int row2, int col2)
{
int sum = 0;
for (int i = row1; i <= row2; i++)
{
sum += sums[i][col2 + 1] - sums[i][col1];//sums[i][col2+1]包含matrix[i][col2] sums[i][col1]不包含matrix[i][col1]
}
return sum;
}
};
c语言
typedef struct
{
int** sums;
int sumsSize;
}NumMatrix;
NumMatrix* numMatrixCreate(int** matrix, int matrixSize, int* matrixColSize)//matrixColSize理解成矩阵的第二维
{
NumMatrix* ret = malloc(sizeof(NumMatrix));
ret->sums = malloc(sizeof(int*)*matrixSize);
ret->sumsSize = matrixSize;
for (int i = 0; i < matrixSize; i++)
{
ret->sums[i] = malloc(sizeof(int)*(matrixColSize[0] + 1));
ret->sums[i][0] = 0;
for (int j = 0; j < matrixColSize[i]; j++)
{
ret->sums[i][j + 1] = ret->sums[i][j] + matrix[i][j];
}
}
return ret;
}
int numMatrixSumRegion(NumMatrix* obj, int row1, int col1, int row2, int col2)
{
int sum = 0;
for (int i = row1; i <= row2; i++)
{
sum += obj->sums[i][col2 + 1] - obj->sums[i][col1];
}
return sum;
}
void numMatrixFree(NumMatrix* obj)
{
for (int i = 0; i < obj->sumsSize; i++)
free(obj->sums[i]);
free(obj->sums);
free(obj);
}
复杂度分析
时间复杂度:
(1)初始化O(mn),每次检索 O(m),其中 m 和 n 分别是矩阵 matrix 的行数和列数。初始化需要遍历矩阵matrix计算二维前缀和,时间复杂度是O(mn)。(2)每次检索需要对二维区域中的每一行计算子数组和,二维区域的行数不超过 m,计算每一行的子数组和的时间复杂度是 O(1),因此每次检索的时间复杂度是 O(m)。
空间复杂度:
O(mn),其中 m 和 n 分别是矩阵 matrix 的行数和列数。需要创建一个 m 行 n+1 列的前缀和数组 sums。
方法二:二维前缀和
方法一虽然利用了前缀和,但是每次检索的时间复杂度是 O(m),仍然没有降到 O(1)。为了将每次检索的时间复杂度降到 O(1),需要使用二维前缀和,在初始化的时候计算二维前缀和数组。
假设 m 和 n 分别是矩阵 matrix 的行数和列数。定义当 0≤i<m 且 0≤j<n 时,f(i,j) 为矩阵matrix 的以 (i,j) 为右下角的子矩阵的元素之和:

当 i=0 或 j=0 时,计算 f(i,j) 只需要对矩阵matrix 的最上边的行和最左边的列分别计算前缀和即可。当 i 和 j 都大于 0 时,如何计算 f(i,j) 的值?
当 i 和 j 都大于 0 时,假设计算 f(i,j) 时已经知道了 f(i−1,j)、f(i,j−1) 和 f(i−1,j−1) 的值。为了计算 f(i,j),自然而然会想到使用 f(i−1,j)、f(i,j−1) 和 matrix[i][j] 的值。
注意到 f(i−1,j) 和 f(i,j−1) 这两项涉及到的矩阵 matrix 的元素有重合,矩阵 matrix 的以 (i−1,j−1) 为右下角的子矩阵都在这两项中出现。因此如果计算 f(i−1,j)+f(i,j−1)+matrix[i][j],则该结果值比 f(i,j) 多了f(i−1,j−1),因此 f(i,j) 的计算如下:


因此在初始化的时候,即可对所有 0≤i<m 和 0≤j<n 计算得到 f(i,j) 的值。





c++代码
class NumMatrix
{
public:
vector<vector<int>> sums;
NumMatrix(vector<vector<int>>& matrix)
{
int m = matrix.size();
if (m > 0)
{
int n = matrix[0].size();
//vector默认初始化为0,所以sums[0][0]默认为0,不需要重新赋初值
//注意二维前缀的第一维size应该为m+1
sums.resize(m+1, vector<int>(n + 1));
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
sums[i + 1][j + 1] = sums[i][j + 1] + sums[i + 1][j] - sums[i][j]+matrix[i][j];
}
}
}
}
int sumRegion(int row1, int col1, int row2, int col2)
{
return sums[row2 + 1][col2 + 1] - sums[row2+1][col1] - sums[row1][col2+1] + sums[row1][col1];
}
};
c语言
typedef struct
{
int** sums;
int sumsSize;
}NumMatrix;
NumMatrix* numMatrixCreate(int** matrix, int matrixSize, int* matrixColSize)
{
NumMatrix* ret = malloc(sizeof(NumMatrix));
ret->sums = malloc(sizeof(int*)*(matrixSize + 1));
ret->sumsSize = matrixSize + 1;
//没太理解为什么不能直接用int n = matrixColSize[0],如果matrixSize为0,matrixColSize[0]难道不为0吗,但实际上并不行。
//我的猜想是如果matrixSize为0,matrixColSize[0]返回的是NULL,只是猜想。知道了再补上理解。
int n = matrixSize ? matrixColSize[0] : 0;
for (int i = 0; i <= matrixSize; i++)
{
ret->sums[i] = malloc(sizeof(int)*(n + 1));
memset(ret->sums[i], 0, sizeof(int)*(n + 1));
}
for (int i = 0; i < matrixSize; i++)
{
for (int j = 0; j < n; j++)
{
ret->sums[i + 1][j + 1] = ret->sums[i][j + 1] + ret->sums[i + 1][j] - ret->sums[i][j] + matrix[i][j];
}
}
return ret;
}
int numMatrixSumRegion(NumMatrix* obj, int row1, int col1, int row2, int col2)
{
return obj->sums[row2 + 1][col2 + 1] - obj->sums[row1][col2 + 1] - obj->sums[row2 + 1][col1] + obj->sums[row1][col1];
}
void numMatrixFree(NumMatrix* obj)
{
for (int i = 0; i < obj->sumsSize; i++)
{
free(obj->sums[i]);
}
free(obj->sums);
free(obj);
}
复杂度分析
时间复杂度:
(1) 初始化 O(mn),每次检索 O(1),其中 m 和 n 分别是矩阵 matrix 的行数和列数。 初始化需要遍历矩阵matrix 计算二维前缀和,时间复杂度是 O(mn)。
(2) 每次检索的时间复杂度是 O(1)。
空间复杂度:
O(mn),其中 m 和 n 分别是矩阵 matrix 的行数和列数。需要创建一个 m+1 行 n+1 列的二维前缀和数组 sums。

159

被折叠的 条评论
为什么被折叠?



