You are given a positive integer n, indicating that we initially have an n x n 0-indexed integer matrix mat filled with zeroes.
You are also given a 2D integer array query. For each query[i] = [row1i, col1i, row2i, col2i], you should do the following operation:
Add 1 to every element in the submatrix with the top left corner (row1i, col1i) and the bottom right corner (row2i, col2i). That is, add 1 to mat[x][y] for for all row1i <= x <= row2i and col1i <= y <= col2i.
Return the matrix mat after performing every query.
Constraints:
1 <= n <= 500
1 <= queries.length <= 10^4
0 <= row1i <= row2i < n
0 <= col1i <= col2i < n
Analysis:
This question looks very straightforward: for each query, we can just update all the elements in the block by adding 1. But the overall time complexity is O(10^4 * n * n) ~ O (25 x 10^6), which cannot pass the large cases in the OJ.
So how can we improve it?
One way is to lower the dimension: from 2D to 1D. Then for the 1D problem, we can solve it using scanning line algorithm. The key observation is that, for each update (or query), we just need to adjust the staring and the ending of the range (for example, the beginning is +1, and the ending is -1), and we do not need to do the updates for every element between the starting and ending. Then we can run a one-time accumulation from left to right to get the actual updates for all the elements.
So similarly, for 2D we can add a labeling for the changes in a block by just update the four corners of the block:
1. the top left corner: +1;
2. the top right corner: -1;
3. the bottom left corner: -1;
4. the bottom right corner: +1.
How do we rationalize this?
1 is for the staring of the change; 2 and 3 means the end of the changes in horizontal and vertical directions, respectively; then what is 4 for?
The reason is simple: by ending the change in 2 and 3, we have double the changing in range staring with 4. But we just need one change. So we need to set it back by adding 1.
After the labeling, we can get the updated values for every elements by performing accumulations like the case in 1D.
The 2D accumulation formula is:
1: res[i][j] += changes[i][j]
2. res[i][j] += res[i-1][j];
3. res[i][j] += res[i][j-1];
4. res[i][j] -= res[i-1][j-1].
res[i][j] is the accumulated sum (like a prefix sum) of the block starting from top left corner (0, 0) to bottom right corner (i, j).
The changes[i][j] is for the changes at the (i, j).
The sum of the block except the element at (i, j) is: res[i-1][j] + res[i[j-1] - res[i-1][j-1]. (why?)
Thus the formula is:
res[i][j] = changes[i][j] + res[i-1][j] + res[i][j-1] - res[i-1][j-1]
which is the sum of the above formula from 1 to 4.
For easier coding, we set the change matrix with one more row and column than that of the element matrix.
See the code below:
class Solution {
public:
vector<vector<int>> rangeAddQueries(int n, vector<vector<int>>& queries) {
vector<vector<int>> res(n, vector<int>(n, 0)), diff(n+1, vector<int>(n+1, 0));
for(auto &a : queries) {
int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3];
++diff[x1][y1];
--diff[x1][y2+1];
--diff[x2+1][y1];
++diff[x2+1][y2+1];
}
for(int i=0; i<n; ++i) {
for(int j=0; j<n; ++j) {
res[i][j] += diff[i][j];
if(i>0) res[i][j] += res[i-1][j];
if(j>0) res[i][j] += res[i][j-1];
if(i>0 && j>0) res[i][j] -= res[i-1][j-1];
}
}
return res;
}
};