二维区域和检索 - 矩阵不可变|刷题打卡

975 阅读2分钟

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (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 。

解题思路一

因为之前刚好做过一维数组用前缀和来求取任意范围数字和的题,大概思路是每一行一维数组可以存下每个位置之前所有数字的和,求取任意位置的时候用后面的和减前面的和就可以了。可以把二维矩阵看成多个一位数组求取就好,直接上代码:

/**
 * @param {number[][]} matrix
 */
var NumMatrix = function(matrix) {
  this.sums = matrix.map((row) => 
  row.reduce((sofar, curr, index) => {
    if(index===0) return [curr];
    return sofar.concat([sofar[index-1]+curr]);
  }, []));
};

/** 
 * @param {number} row1 
 * @param {number} col1 
 * @param {number} row2 
 * @param {number} col2
 * @return {number}
 */
NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
  let sum = 0;
  for(let i = row1;i<=row2;i++) {
    // console.log(this.sums[i][col2],this.sums[i][col1-1])
    sum += (this.sums[i][col2] - (this.sums[i][col1-1]||0));
  }
  return sum;
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * var obj = new NumMatrix(matrix)
 * var param_1 = obj.sumRegion(row1,col1,row2,col2)
 */

解题思路二

但是通过一维数组前缀和的方法,在求和的时候依然要遍历矩阵高度 m 次。既然一维数组可以直接存前缀和,那到二维矩阵的情况,有没有可能在矩阵的每个位置上存下来任意右下角位置的矩阵的前缀和呢?

考虑以矩阵中任何一个位置为右下角,那么它包括其左上方所有元素的和,应当是这个位置上方所有元素的和,加上左边所有元素的和。但是在相加的时候,会发现左上方有一部分会重复相加,所以需要减掉这一部分。

如图,只需要红色部分加上蓝色和黄色的部分,再减去黄色和蓝色重叠的部分就好了。

这样,我们存下来的矩阵中的每个位置的值,就是以当前位置为右下角,直到矩阵左上角所有元素的和了。

那在求取的时候,只需要用右下角的矩阵和,减去左边的矩阵和与上面的矩阵和即可。同理,左上角的部分会多减一次,所以需要加回来。

注意处理超出边界的情况,超出边界的情况值应当为0,因为没有元素可以加。

/**
 * @param {number[][]} matrix
 */
var NumMatrix = function(matrix) {
  this.sums = matrix.reduce((sofar, row, rIndex) => 
  { 
    return sofar.concat([row.reduce((sofarRow, curr, index) => {
      const leftSum = index === 0 ? 0 : sofarRow[index-1];
      const topSum = rIndex === 0 ? 0 : sofar[rIndex-1][index];
      const duplicateSum = index===0 || rIndex===0 ? 0 : sofar[rIndex-1][index-1];
      const sum = matrix[rIndex][index] + leftSum + topSum - duplicateSum;
      return sofarRow.concat([sum]);
    }, [])])
  },[]);
};

/** 
 * @param {number} row1 
 * @param {number} col1 
 * @param {number} row2 
 * @param {number} col2
 * @return {number}
 */
NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
  const left = col1 === 0 ? 0 : this.sums[row2][col1-1];
  const top = row1 === 0 ? 0 : this.sums[row1-1][col2];
  const duplicate = col1 ===0 || row1===0 ? 0 : this.sums[row1-1][col1-1];
  return this.sums[row2][col2] - left - top + duplicate;
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * var obj = new NumMatrix(matrix)
 * var param_1 = obj.sumRegion(row1,col1,row2,col2)
 */

总结

前缀和是一种面对特定问题反复求解,预先处理原数据的思想。了解了思想以后,理论上可以应用到更多维的情况中,当然需要处理的边界条件和重复情况也会更多。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情