【前端也得会算法】- 304. 二维区域和检索 - 矩阵不可变 [ 中等 ]

162 阅读1分钟

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
};

image.png

效率慢的出奇,结合示例发现,该题多次调用sumRegion函数,双循环属实太慢

方法二 巧用几何法

  • 原理。通过数学的几何算法来算矩阵之和。

image.png 解释:求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)的值,则通过下面这种方法计算: image.png

如果求 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]
};

image.png

由于代码比较长。将数组拓展一下,避免屏蔽0的情况。改代码为: image.png

/**
 * @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]
};

image.png 速度快了一点点

方案比较

  1. 方案一使用的是主要是通过双循环进行矩阵的和计算。频繁调用sumRegion的话就会很慢。毕竟每一次都在双循环中,时间复杂度为O(n2)

  2. 方案二使用的是通过数学的几何算法来算矩阵之和。巧妙调用一次双循环将矩阵转化为数字的计算,时间复杂度为O(1)

三、总结

  • 此题可以遍历计算法巧用几何法两种方案
  • 遍历计算法主要是通过通过双循环进行矩阵的和计算。
  • 巧用几何法使用的是通过数学的几何算法来算矩阵之和。

文中如有错误,欢迎在评论区指正