304. 二维区域和检索 - 矩阵不可变

304. 二维区域和检索 - 矩阵不可变

题目描述

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙叙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值