Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
一、题目描述:
给定一个二维矩阵 matrix,以下类型的多个请求:
- 计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:
- NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
- int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。
示例
输入:
["NumMatrix","sumRegion","sumRegion","sumRegion"]
[[[[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]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]
解释:
NumMatrix numMatrix = new NumMatrix([[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]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)
提示:
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 200
- -105 <= matrix[i][j] <= 105
- 0 <= row1 <= row2 < m
- 0 <= col1 <= col2 < n
- 最多调用 104 次 sumRegion 方法
二、题解:
方法一 遍历计算法
- 原理。主要是通过双循环进行矩阵的和计算。
代码:
/**
* @param {number[][]} matrix
*/
var NumMatrix = function(matrix) {
this.matrix = matrix
};
/**
* @param {number} row1
* @param {number} col1
* @param {number} row2
* @param {number} col2
* @return {number} sum
*/
NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
var sum = 0
if(row1<=row2 && col1<= col2){
// 列循环
for(let i = row1;i<=row2;i++){
for(let j = col1;j<=col2;j++){
sum += this.matrix[i][j]
}
}
}
return sum
};
效率慢的出奇,结合示例发现,该题多次调用sumRegion函数,双循环属实太慢
方法二 巧用几何法
- 原理。通过数学的几何算法来算矩阵之和。
解释:求S(A,D)的面积,则通过S(O,D)减去S(O,E),再减去S(O,F),由于多减了一遍S(O,G),再加回来即可。
即推导得出: 如果要求 [row1, col1][row1,col1] 到 [row2, col2][row2,col2] 的子矩形的面积的话,用 preSum 对应了以下的递推公式:
preSum[row2][col2]−preSum[row2][col1−1]−preSum[row1−1][col2]+preSum[row1−1][col1−1]
因为存在row1,col1为0的情况,所以需要变式:
if(row1===0 && col1 ===0){
return this.preSum[row2][col2]
}
if(row1===0 && col1 !==0){
return this.preSum[row2][col2] - this.preSum[row2][col1 - 1]
}
if(row1!==0 && col1 ===0){
return this.preSum[row2][col2] - this.preSum[row1 - 1][col2]
}
那么如果这样计算的话,首先得把原数组改成第N个方块内是S(O,N)的值,则通过下面这种方法计算:
如果求 preSum[i][j]preSum[i][j] 表示的话,对应了以下的递推公式:
preSum[i][j]=preSum[i−1][j]+preSum[i][j−1]−preSum[i−1][j−1]+matrix[i][j]
因为存在i,j为0的情况,所以需要变式:
if(i===0 && j ===0){
this.preSum[i][j] = matrix[i][j]
continue
}
if(i===0 && j !==0){
this.preSum[i][j] = this.preSum[i][j-1]+matrix[i][j]
continue
}
if(i!==0 && j ===0){
this.preSum[i][j] = this.preSum[i-1][j]+matrix[i][j]
continue
}
代码:
/**
* @param {number[][]} matrix
*/
var NumMatrix = function(matrix) {
this.preSum = new Array(matrix.length)
for(let i = 0;i<matrix.length;i++){
this.preSum[i] = new Array(matrix[i].length)
for(let j=0;j<matrix[i].length;j++) {
if(i===0 && j ===0){
this.preSum[i][j] = matrix[i][j]
continue
}
if(i===0 && j !==0){
this.preSum[i][j] = this.preSum[i][j-1]+matrix[i][j]
continue
}
if(i!==0 && j ===0){
this.preSum[i][j] = this.preSum[i-1][j]+matrix[i][j]
continue
}
this.preSum[i][j] = this.preSum[i-1][j]+this.preSum[i][j-1]
-this.preSum[i-1][j-1]+matrix[i][j]
}
}
};
/**
* @param {number} row1
* @param {number} col1
* @param {number} row2
* @param {number} col2
* @return {number} sum
*/
NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
if(row1===0 && col1 ===0){
return this.preSum[row2][col2]
}
if(row1===0 && col1 !==0){
return this.preSum[row2][col2] - this.preSum[row2][col1 - 1]
}
if(row1!==0 && col1 ===0){
return this.preSum[row2][col2] - this.preSum[row1 - 1][col2]
}
return this.preSum[row2][col2] - this.preSum[row2][col1 - 1] - this.preSum[row1 - 1][col2] + this.preSum[row1 - 1][col1 - 1]
};
由于代码比较长。将数组拓展一下,避免屏蔽0的情况。改代码为:
/**
* @param {number[][]} matrix
*/
var NumMatrix = function(matrix) {
let m = matrix.length
let n = matrix[0].length
this.preSum = 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.preSum[i+1][j+1] = this.preSum[i][j+1]+this.preSum[i+1][j]-this.preSum[i][j]+matrix[i][j]
}
}
};
/**
* @param {number} row1
* @param {number} col1
* @param {number} row2
* @param {number} col2
* @return {number} sum
*/
NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
return this.preSum[row2+1][col2+1] - this.preSum[row2+1][col1] - this.preSum[row1][col2+1] + this.preSum[row1][col1]
};
速度快了一点点
方案比较
-
方案一使用的是主要是通过双循环进行矩阵的和计算。频繁调用sumRegion的话就会很慢。毕竟每一次都在双循环中,时间复杂度为O(n2)
-
方案二使用的是通过数学的几何算法来算矩阵之和。巧妙调用一次双循环将矩阵转化为数字的计算,时间复杂度为O(1)
三、总结
- 此题可以遍历计算法和巧用几何法两种方案
- 遍历计算法主要是通过通过双循环进行矩阵的和计算。
- 巧用几何法使用的是通过数学的几何算法来算矩阵之和。
文中如有错误,欢迎在评论区指正