前言
昨天做了[LeetCode0303题区域和检索 - 数组不可变] | 刷题打卡和[LeetCode1200. 最小绝对差] | 刷题打卡两道题,303题是昨天的每日一题考察的是前缀和,果然今天的每日一题也是,其实大家从每天的每日一题点进相似题目,如果看到官方题解更新了,那这道题大概率就是第二天的每日一题了。
题目描述
这题是LeetCode0304题二维区域和检索 - 矩阵不可变,也是今天的每日一题。
给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。
上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。
示例:
给定 matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12
说明:
- 你可以假设矩阵不可变。
- 会多次调用 sumRegion 方法。
- 你可以假设 row1 ≤ row2 且 col1 ≤ col2。
解题思路
看到题目就想到了昨天的每日一题前缀和,然后看了下题目描述考察的也是前缀和,那还等什么?直接按照昨天的思路开始来吧!
定义一个sum新数组,和给定数组matrix一样,只不过全部填充为0,这里记得要先判断matrix的length,我第一遍做的时候没有对matrix的length进行判断就出错了,考虑到sum[0][0]的情况后面还需要特殊处理,所以我们在定义sum的时候sum.length = matrix.length+1,sum[0].length = matrix[0].length+1。
我们以示例为数据,则sum如下所示:
[
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0 ]
]
然后就是循环matrix计算前缀和,前面我们考虑到sum[0][0]的情况对sum进行了处理,所以坐标[i, j]可以在sum中表示为sum[i+1][j+1],我们以matrix[1][1]为例它的前缀和是14(从图中一个一个算,这是最笨的方法了吧),可以表示为sum[2][2],如下所示:
sum[2][2] = sum[1][2] + sum[2][1] + matrix[1][1]
// sum[1][2]表示的是当前位置上一行的前缀和即matrix[0][1]的前缀和,从图中可以看出是3
// sum[2][1]表示这一行当前位置的前缀和即matrix[1][0]的前缀和,从图中可以看出是8
// 加上当前位置 matrix[1][1] = 6
// 得到 17
这里得到的17和我们上面用笨方法得到的14不对啊,是方法错了吗?
不是的,我们仔细看会发现,sum[1][2]和sum[2][1]是不是都计算了一遍sum[1][1],这里进行了重复计算前缀和,所以要减掉一个sum[1][1],即如下所示:
sum[2][2] = sum[1][2] + sum[2][1] - sum[1][1] + matrix[1][1] = 3 + 8 - 3 + 6 = 14
即:
this.sum[i + 1][j + 1] = this.sum[i][j + 1] + this.sum[i + 1][j] - this.sum[i][j] + matrix[i][j];
然后我们以sumRegion(2, 1, 4, 3)为例来演示,即求matrix[2][1]到matrix[4][3]之间的元素和,有的同学会说这里直接这样做:
sum[5][4] - sum[3][2]
// 等于 19
可以看出这样是不对的,看下图:
matrix[4][3]的前缀和是matrix[0][0]到matrix[4][3],先减掉matrix[1][3],剩下的就是matrix[2][0]到matrix[4][3]的元素和了,然后再减去matrix[4][0],剩下的就是matrix[2][1]到matrix[4][3]的元素和吗?当然不是,我们可以发现我们多减了一个matrix[1][0]的元素和,所以要再加上matrix[1][0]的元素和,最后得到的就是matrix[2][1]到matrix[4][3]之间的元素和。
复杂度分析
-
时间复杂度:初始化 O(mn)O(mn),每次检索 O(1)O(1),其中 mm 和 nn 分别是矩阵 \textit{matrix}matrix 的行数和列数。
初始化需要遍历矩阵 \textit{matrix}matrix 计算二维前缀和,时间复杂度是 O(mn)O(mn)。
每次检索的时间复杂度是 O(1)O(1)。
-
空间复杂度:O(mn)O(mn),其中 mm 和 nn 分别是矩阵 \textit{matrix}matrix 的行数和列数。需要创建一个 m+1m+1 行 n+1n+1 列的二维前缀和数组 \textit{sums}sums。
解题代码
/**
* @param {number[][]} matrix
*/
var NumMatrix = function (matrix) {
const m = matrix.length;
if (m > 0) {
const n = matrix[0].length;
this.sum = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
this.sum[i + 1][j + 1] = this.sum[i][j + 1] + this.sum[i + 1][j] - this.sum[i][j] + matrix[i][j];
}
}
}
};
/**
* @param {number} row1
* @param {number} col1
* @param {number} row2
* @param {number} col2
* @return {number}
*/
NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) {
return this.sum[row2 + 1][col2 + 1] - this.sum[row1][col2 + 1] - this.sum[row2 + 1][col1] + this.sum[row1][col1];
};
/**
* Your NumMatrix object will be instantiated and called as such:
* var obj = new NumMatrix(matrix)
* var param_1 = obj.sumRegion(row1,col1,row2,col2)
*/
总结
这道题和昨天303的题虽然都是前缀和,但是303是一维前缀和,这道题是二维前缀和,当然用一维也可以(具体的解法就要大家自己去思考了)。如果你觉的还不错的话,给我点个赞吧💐
加油!好兄弟们!💪💪💪