这一题是https://blog.csdn.net/chaochen1407/article/details/86572593 和 https://blog.csdn.net/chaochen1407/article/details/86610960的共同延伸。在2d - immutable里面,我们的做法的核心是记录每一个从(0, 0)开始到某点的区域面积,然后返回类似return sum[right][Top] - sum[left][Top] - sum[right][Bot] + sum[left][Bot];即可。而1d - mutable里面,我们binary index tree的做法是可以返回 0 ~ i的和 prefixSum(i),然后求range(i ,j) 就是prefixSum(j) - prefixSum(i - 1)。所以到了2d-mutable这里,我们把两者结合一下就好了。嗯,我们可以假想一下,如果binary index tree也可以2d化,然后我们可以 通过一个相对快速的方式拿到bitTreeSum(i, j) 对应2d-immutable里面的 sum[i][j]。那就可以了。这就是2d - binary index tree的基本思路。
但是!最开始,我还是想举出另一个相对简单的解法。
这个解法依旧是在update和建树的时候缓存某些东西cols,cols是一个二维数组,大小和这个2d array一样大,cols[i][j]存的东西就是matrix[i][0] ... matrix[i][j]的和。所以每次update(row, col, val)的时候,都要进行一个o(n)的操作去更新cols[row][0]... cols[row][col]。这样的话,当求和的时候sumRegion(row1, col1, row2, col2)的时候,所要做的就很简单了,就是求(col[row1][col2] - col[row1][col1 - 1]) + (col[row1+ 1][col2] - col[row1 + 1][col1 - 1]) + .. (col[row2][col2] - col[row2][col1 - 1)即可,这个过程可以认为是O(m)。
根据上述描述,可以得到比较简单的代码如下:
int[][] cols;
public NumMatrix(int[][] matrix) {
if (matrix.length == 0) return;
this.cols = new int[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
this.cols[i][j] = matrix[i][j];
if (j != 0) {
this.cols[i][j] += this.cols[i][j - 1];
}
}
}
}
public void update(int row, int col, int val) {
int originValue = this.cols[row][col];
if (col > 0) originValue -= this.cols[row][col - 1];
int delta = val - originValue;
for (int i = col; i < this.cols[row].length; i++) {
this.cols[row][i] += delta;
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
int result = 0;
for (int i = row1; i <= row2; i++) {
result += this.cols[i][col2];
if (col1 > 0) {
result -= this.cols[i][col1 - 1];
}
}
return result;
}
介绍完简单的就来难的。回到我们之前所说的2D binary index tree。整体操作基本就是1d binary index tree是二维for循环。在1d binary index tree的时候,bitArr[i]的子节点是bitArr[i + (i & -i)],父亲节点是bitArr[i - (i & -i)]。而对于一个2d binary index tree来说,bitArr[row][col]的父子节点的结构有点难理解,你可以认为遍历完一个bitArr[row][col]的所有子孙节点就是分两个维度在跑一维的binary index tree。所以就是一个二维for循环,外循环在跑row + (row & -row),内循环在跑col + (col & -col)。这样就能遍历完bitArr[row][col]的子孙节点了。求和和一维binary index tree也是一样的,可以直接求到的是以(0, 0)为起点到(row, col)的区域和,我们称之为getAreaSum。同样跑的是一个二维for循环,外层是row - (row & -row), 里层是 col - (col & -col),把所有遍历过的节点全加起来即可。而这里的getAreaSum(row, col),其实就等同于2d-immutable里面解法的sum[row][col],只是复杂度是O(logmn),而不是o(1)。所以sumRegion(row1, col1, row2, col2)其实就是getAreaSum(row2, col2) - getAreaSum(row1 - 1, col2) - getAreaSum(row2, col1 - 1) + getAreaSum(row1 -1, col1 - 1)。和1维的binary index tree的建树是一样的,就相当于对每个节点call一个update,这个update的delta值就是matrix里面的值。我尝试过像1维的binary index tree那样把建树的复杂度优化成O(mn)但是我失败了,所以建树的算法复杂度目前就是O(mn * logmn)。
根据上述描述,可以得到代码是这样的
int[][] bitArr;
int[][] origin;
public NumMatrix(int[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) return;
this.origin = new int[matrix.length][matrix[0].length];
this.bitArr = new int[matrix.length + 1][matrix[0].length + 1];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
this.update(i, j, matrix[i][j]);
}
}
}
public void update(int row, int col, int val) {
int delta = val - this.origin[row][col];
this.origin[row][col] = val;
for (int i = row + 1; i < this.bitArr.length; i = i + (i & -i)) {
for (int j = col + 1; j < this.bitArr[0].length; j = j + (j & -j)) {
this.bitArr[i][j] += delta;
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return this.getAreaSum(row2, col2) - this.getAreaSum(row1 - 1, col2)
- this.getAreaSum(row2, col1 - 1) + this.getAreaSum(row1 - 1, col1 - 1);
}
public int getAreaSum(int row, int col) {
int result = 0;
for (int i = row + 1; i > 0; i = i - (i & -i)) {
for (int j = col + 1; j > 0; j = j - (j & -j)) {
result += this.bitArr[i][j];
}
}
return result;
}