《剑指 Offer》专项突破版 - 面试题 13 : 二维子矩阵的数字之和(C++ 实现)- 二维前缀和

题目链接:LCR 013. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode)

题目

输入一个二维矩阵,如何计算给定左上角坐标和右下角坐标的子矩阵的数字之和?对于同一个二维矩阵,计算子矩阵的数字之和的函数可能由于输入不同的坐标而反复调用多次。

例如,对于下图中的二维矩阵,输入左上角坐标 (2, 1) 和右下角坐标 (4, 3),该函数输出 8(红色框的子矩阵的数字之和);输入左上角坐标 (1, 1) 和右下角坐标 (2, 2),该函数输出 11(绿色框的子矩阵的数字之和);输入左上角坐标 (1, 2) 和右下角坐标 (2, 4),该函数输出 12(蓝色框的子矩阵的数字之和)。

《剑指 Offer》专项突破版 - 面试题 13 : 二维子矩阵的数字之和(C++ 实现)- 二维前缀和_第1张图片

分析

如果不考虑时间复杂度,则采用蛮力法用两个嵌套的循环总是可以求出一个二维矩阵的数字之和。如果矩阵的行数和列数分别是 m 和 n,那么这种蛮力法的时间复杂度是 O(mn)。

只是这个题目提到,对于一个二维矩阵,可能由于输入不同的坐标而反复求不同子矩阵的数字之和,这说明应该优化求和的过程,要尽可能快地实现子矩阵的数字求和。

如果仔细分析子矩阵的数字之和的规律,就可以发现左上角坐标为 (r1, c1)、右下角坐标为 (r2, c2) 的子矩阵的数字之和可以用 4 个左上角坐标为 (0, 0) 的子矩阵的数字之和求得。下图中的阴影部分表示左上角坐标为 (r1, c1)、右下角坐标为 (r2, c2) 的子矩阵。该矩阵的数字之和等于左上角坐标为 (0, 0)、右下角坐标为 (r2, c2) 的子矩阵的数字之和减去左上角坐标为 (0, 0)、右下角坐标为 (r1 - 1, c2) 的子矩阵的数字之和,再减去左上角坐标为 (0, 0)、右下角坐标为 (r2, c1 - 1) 的子矩阵的数字之和,最后加上左上角坐标为 (0, 0)、右下角坐标为 (r1 - 1, c1 - 1) 的子矩阵的数字之和

《剑指 Offer》专项突破版 - 面试题 13 : 二维子矩阵的数字之和(C++ 实现)- 二维前缀和_第2张图片

因此,可以在预处理阶段求出从左上角坐标为 (0, 0) 到每个右下角坐标的子矩阵的数字之和。首先创建一个和输入矩阵大小相同的辅助矩阵 sums,该矩阵中的坐标 (i, j) 的数值为输入矩阵中从左上角坐标 (0, 0) 到右下角坐标 (i, i) 的子矩阵的数字之和

有了这个辅助矩阵 sums,再求左上角坐标为 (r1, c1)、右下角坐标为 (r2, c2) 的子矩阵的数字之和就变得比较容易。该子矩阵的数字之和等于 sums[r2][c2] - sums[r1 - 1][c2] - sums[r2][c1 - 1] + sums[r1 - 1][c1 - 1]

下面分析如何生成辅助矩阵 sums,即求得数组中的每个数字 sums[i][j]。按照生成辅助矩阵 sums 的规则,sums[i][j] 的值等于输入矩阵中从左上角坐标为 (0, 0) 到右下角坐标为 (i, j) 的子矩阵的数字之和。可以把左上角坐标为 (0, 0) 到右下角坐标为 (i, j) 的子矩阵的数字看成由两部分组成。第 1 部分是从左上角坐标为 (0, 0) 到右下角坐标为 (i - 1, j) 的子矩阵,该子矩阵的数字之和等于 sums[i - 1][j]。第 2 部分是输入矩阵中第 i 行中列号从 0 到 j 的所有数字,如果按照从左到右的顺序计算 sums[i][j],则可以逐个累加第 i 行的数字,从而得到子矩阵第 2 部分的数字之和

class NumMatrix {
private:
    vector> sums;
public:
    NumMatrix(vector>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        sums.resize(m + 1, vector(n + 1, 0));
        for (int i = 0; i < m; ++i)
        {
            int rowSum = 0;
            for (int j = 0; j < n; ++j)
            {
                rowSum += matrix[i][j];
                sums[i + 1][j + 1] = sums[i][j + 1] + rowSum;
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2 + 1][col2 + 1] - sums[row1][col2 + 1] - sums[row2 + 1][col1] + sums[row1][col1];
    }
};

注意:如果输入的行数和列数分别是 m 和 n,那么辅助数组 sums 的行数和列数分别为 m + 1 和 n + 1,这样只是为了简化代码逻辑。如果用公式 sums[r2][c2] - sums[r1 - 1][c2] - sums[r2][c1 - 1] + sums[r1 - 1][c1 - 1] 求解左上角坐标为 (r1, c1)、右下角坐标为 (r2, c2) 的子矩阵的数字之和,由于坐标值 r1 或 c1 有可能等于 0,因此 r1 - 1 或 c1 - 1 可能是负数,不再是有效数组的下标。如果在矩阵的最上面增加一行,最左面增加一列,这样就不必担心出现数组下标为 -1 的情形

你可能感兴趣的:(数据结构,矩阵,c++,线性代数,数据结构,算法,leetcode,面试)