🥈Medium

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)
image.png
上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。

示例

  1. 给定 matrix = [
  2. [3, 0, 1, 4, 2],
  3. [5, 6, 3, 2, 1],
  4. [1, 2, 0, 1, 5],
  5. [4, 1, 0, 1, 7],
  6. [1, 0, 3, 0, 5]
  7. ]
  8. sumRegion(2, 1, 4, 3) -> 8
  9. sumRegion(1, 1, 2, 2) -> 11
  10. sumRegion(1, 2, 2, 4) -> 12

提示

  • 你可以假设矩阵不可变。
  • 会多次调用 sumRegion 方法。
  • 你可以假设 row1 ≤ row2col1 ≤ col2

题解

此题就是303的二维化:
🥉303. 区域和检索 - 数组不可变
303,因为是一维,用暴力一次时间复杂度为🥈304. 二维区域和检索 - 矩阵不可变 - 图2,但变成二维之后,一次暴力时间复杂度就变成🥈304. 二维区域和检索 - 矩阵不可变 - 图3,不太可取。同样需要前缀和进行优化。

一维前缀和

初始化的时候,对矩阵的每一行计算前缀和,检索时对二维区域中每一行计算子数组和,然后对每一行的子数组和计算总和

具体实现方面,创建 行🥈304. 二维区域和检索 - 矩阵不可变 - 图4列的二维数组🥈304. 二维区域和检索 - 矩阵不可变 - 图5,其中 🥈304. 二维区域和检索 - 矩阵不可变 - 图6🥈304. 二维区域和检索 - 矩阵不可变 - 图7分别是矩阵 🥈304. 二维区域和检索 - 矩阵不可变 - 图8的行数和列数,🥈304. 二维区域和检索 - 矩阵不可变 - 图9🥈304. 二维区域和检索 - 矩阵不可变 - 图10的前缀和数组。将 🥈304. 二维区域和检索 - 矩阵不可变 - 图11的列数设为 🥈304. 二维区域和检索 - 矩阵不可变 - 图12 的目的是为了方便计算每一行的子数组和,不需要对 🥈304. 二维区域和检索 - 矩阵不可变 - 图13的情况特殊处理。

Python

class NumMatrix:
    def __init__(self, matrix: List[List[int]]):
        m, n = len(matrix), (len(matrix[0]) if matrix else 0)
        self.sums = [[0] * (n + 1) for _ in range(m)]
        _sums = self.sums

        for i in range(m):
            for j in range(n):
                _sums[i][j + 1] = _sums[i][j] + matrix[i][j]

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        _sums = self.sums

        total = sum(_sums[i][col2 + 1] - _sums[i][col1] for i in range(row1, row2 + 1))
        return total

JavaScript

var NumMatrix = function(matrix) {
    const m = matrix.length;
    if (m > 0) {
        const n = matrix[0].length;
        this.sums = new Array(m).fill(0).map(() => new Array(n + 1).fill(0));
        for (let i = 0; i < m; i++) {
            for (let j = 0; j < n; j++) {
                this.sums[i][j + 1] = this.sums[i][j] + matrix[i][j];
            }
        }
    }
};

NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
    let sum = 0;
    for (let i = row1; i <= row2; i++) {
        sum += this.sums[i][col2 + 1] - this.sums[i][col1];
    }
    return sum;
};

复杂度分析

  • 时间复杂度:初始化 🥈304. 二维区域和检索 - 矩阵不可变 - 图14,每次检索 🥈304. 二维区域和检索 - 矩阵不可变 - 图15,其中 🥈304. 二维区域和检索 - 矩阵不可变 - 图16🥈304. 二维区域和检索 - 矩阵不可变 - 图17分别是矩阵🥈304. 二维区域和检索 - 矩阵不可变 - 图18的行数和列数。初始化需要遍历矩阵 🥈304. 二维区域和检索 - 矩阵不可变 - 图19计算二维前缀和,时间复杂度是 🥈304. 二维区域和检索 - 矩阵不可变 - 图20。每次检索需要对二维区域中的每一行计算子数组和,二维区域的行数不超过 🥈304. 二维区域和检索 - 矩阵不可变 - 图21,计算每一行的子数组和的时间复杂度是 🥈304. 二维区域和检索 - 矩阵不可变 - 图22,因此每次检索的时间复杂度是🥈304. 二维区域和检索 - 矩阵不可变 - 图23
  • 空间复杂度:🥈304. 二维区域和检索 - 矩阵不可变 - 图24,其中 🥈304. 二维区域和检索 - 矩阵不可变 - 图25🥈304. 二维区域和检索 - 矩阵不可变 - 图26分别是矩阵🥈304. 二维区域和检索 - 矩阵不可变 - 图27的行数和列数。需要创建一个🥈304. 二维区域和检索 - 矩阵不可变 - 图28🥈304. 二维区域和检索 - 矩阵不可变 - 图29列的前缀和数组 🥈304. 二维区域和检索 - 矩阵不可变 - 图30

二维前缀和

👆上面的方法虽然用了前缀和,但每次检索时间复杂度为🥈304. 二维区域和检索 - 矩阵不可变 - 图31,有没有办法降到🥈304. 二维区域和检索 - 矩阵不可变 - 图32呢?

也就只能用二维前缀和了!

🥈304. 二维区域和检索 - 矩阵不可变 - 图33🥈304. 二维区域和检索 - 矩阵不可变 - 图34分别是矩阵🥈304. 二维区域和检索 - 矩阵不可变 - 图35的行数和列数。定义当🥈304. 二维区域和检索 - 矩阵不可变 - 图36🥈304. 二维区域和检索 - 矩阵不可变 - 图37时,🥈304. 二维区域和检索 - 矩阵不可变 - 图38为矩阵🥈304. 二维区域和检索 - 矩阵不可变 - 图39的以🥈304. 二维区域和检索 - 矩阵不可变 - 图40为右下角的子矩阵元素之和:
🥈304. 二维区域和检索 - 矩阵不可变 - 图41

🥈304. 二维区域和检索 - 矩阵不可变 - 图42🥈304. 二维区域和检索 - 矩阵不可变 - 图43时,计算🥈304. 二维区域和检索 - 矩阵不可变 - 图44只需要对矩阵🥈304. 二维区域和检索 - 矩阵不可变 - 图45最上边的行和最左边的列分别计算前缀即可。但🥈304. 二维区域和检索 - 矩阵不可变 - 图46🥈304. 二维区域和检索 - 矩阵不可变 - 图47时,就不是那么好计算🥈304. 二维区域和检索 - 矩阵不可变 - 图48了。但如果知道🥈304. 二维区域和检索 - 矩阵不可变 - 图49🥈304. 二维区域和检索 - 矩阵不可变 - 图50🥈304. 二维区域和检索 - 矩阵不可变 - 图51就容易多了:

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

具体推导如下:

🥈304. 二维区域和检索 - 矩阵不可变 - 图53
🥈304. 二维区域和检索 - 矩阵不可变 - 图54
🥈304. 二维区域和检索 - 矩阵不可变 - 图55
🥈304. 二维区域和检索 - 矩阵不可变 - 图56
🥈304. 二维区域和检索 - 矩阵不可变 - 图57

所以初始化的时候,就可以对所有🥈304. 二维区域和检索 - 矩阵不可变 - 图58🥈304. 二维区域和检索 - 矩阵不可变 - 图59计算出🥈304. 二维区域和检索 - 矩阵不可变 - 图60
示意图如下:
image.png

所以和一维前缀和一样,创建🥈304. 二维区域和检索 - 矩阵不可变 - 图62🥈304. 二维区域和检索 - 矩阵不可变 - 图63列的二维数组 sums,其中 🥈304. 二维区域和检索 - 矩阵不可变 - 图64的值为🥈304. 二维区域和检索 - 矩阵不可变 - 图65的值。

将sums 的行数和列数分别设为🥈304. 二维区域和检索 - 矩阵不可变 - 图66🥈304. 二维区域和检索 - 矩阵不可变 - 图67的目的是为了方便计算 🥈304. 二维区域和检索 - 矩阵不可变 - 图68,不需要对🥈304. 二维区域和检索 - 矩阵不可变 - 图69🥈304. 二维区域和检索 - 矩阵不可变 - 图70的情况特殊处理。此时有:

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

Python

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        m, n = len(matrix), (len(matrix[0]) if matrix else 0)
        self.sums = [[0] * (n + 1) for _ in range(m + 1)]
        _sums = self.sums

        for i in range(m):
            for j in range(n):
                _sums[i + 1][j + 1] = _sums[i][j + 1] + _sums[i + 1][j] - _sums[i][j] + matrix[i][j]

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        _sums = self.sums

        return _sums[row2 + 1][col2 + 1] - _sums[row1][col2 + 1] - _sums[row2 + 1][col1] + _sums[row1][col1]

JavaScript

/**
 * @param {number[][]} matrix
 */
var NumMatrix = function(matrix) {
    const m = matrix.length
    if (m > 0) {
        let n = matrix[0].length
        this.sums = 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.sums[i+1][j+1] = this.sums[i][j+1]+this.sums[i+1][j]-this.sums[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.sums[row2+1][col2+1]-this.sums[row1][col2+1]-this.sums[row2+1][col1]+this.sums[row1][col1]

};

复杂度分析

  • 时间复杂度:初始化 🥈304. 二维区域和检索 - 矩阵不可变 - 图72,每次检索 🥈304. 二维区域和检索 - 矩阵不可变 - 图73,其中 🥈304. 二维区域和检索 - 矩阵不可变 - 图74🥈304. 二维区域和检索 - 矩阵不可变 - 图75分别是矩阵 🥈304. 二维区域和检索 - 矩阵不可变 - 图76的行数和列数。初始化需要遍历矩阵🥈304. 二维区域和检索 - 矩阵不可变 - 图77计算二维前缀和,时间复杂度是🥈304. 二维区域和检索 - 矩阵不可变 - 图78。每次检索的时间复杂度是 🥈304. 二维区域和检索 - 矩阵不可变 - 图79

  • 空间复杂度:🥈304. 二维区域和检索 - 矩阵不可变 - 图80,其中 🥈304. 二维区域和检索 - 矩阵不可变 - 图81🥈304. 二维区域和检索 - 矩阵不可变 - 图82分别是矩阵 🥈304. 二维区域和检索 - 矩阵不可变 - 图83的行数和列数。需要创建一个🥈304. 二维区域和检索 - 矩阵不可变 - 图84🥈304. 二维区域和检索 - 矩阵不可变 - 图85列的二维前缀和数组 🥈304. 二维区域和检索 - 矩阵不可变 - 图86