2D prefix and difference
2D prefix sums answer any submatrix sum in O(1) after an O(R·C) build, and 2D difference arrays apply many rectangle updates in O(1) each. Both rely on inclusion-exclusion over four corners.
Learning goals
- Build a 2D prefix sum table
- Answer submatrix queries with four lookups
- Apply rectangle updates with four corner edits
Prerequisites
2D prefix sums
Let p[r][c] be the sum of all cells in rows 0..r-1 and columns 0..c-1, with an extra row and column of zeros. Then p[r][c] = g[r-1][c-1] + p[r-1][c] + p[r][c-1] - p[r-1][c-1]: the cell, the block above, the block to the left, minus the overlap that both counted. The build is O(R·C).
Watch the table fill; each step shows the overlap being subtracted.
1int R = g.length, C = g[0].length;2long[][] p = new long[R + 1][C + 1];3for (int r = 1; r <= R; r++)4for (int c = 1; c <= C; c++)5p[r][c] = g[r - 1][c - 1] + p[r - 1][c] + p[r][c - 1] - p[r - 1][c - 1];
2D prefix sums and submatrix queries
Interactive · edit the inputgrid · 4 × 5
The prefix matrix has one extra row and column of zeros, so every query formula works without special cases.
1int[][] p = new int[R + 1][C + 1]; // p[r][c] = sum of g[0..r-1][0..c-1]2for (int r = 1; r <= R; r++)3for (int c = 1; c <= C; c++)4p[r][c] = g[r - 1][c - 1] + p[r - 1][c] + p[r][c - 1] - p[r - 1][c - 1]; // inclusion-exclusion5static int sum(int[][] p, int r1, int c1, int r2, int c2) { // inclusive corners6return p[r2 + 1][c2 + 1] - p[r1][c2 + 1] - p[r2 + 1][c1] + p[r1][c1];7}
Live state
R
3
C
4
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | R=3 C=4 | The prefix matrix has one extra row and column of zeros, so every query formula works without special cases. |
| 2 | L4 | r=1 c=1 g[r-1][c-1]=3 p[r][c]=3 | p[1][1] = 3 + above 0 + left 0 - overlap 0 = 3. The overlap was counted twice, so subtract it once. |
| 3 | L4 | r=1 c=2 g[r-1][c-1]=0 p[r][c]=3 | p[1][2] = 0 + above 0 + left 3 - overlap 0 = 3. The overlap was counted twice, so subtract it once. |
| 4 | L4 | r=1 c=3 g[r-1][c-1]=1 p[r][c]=4 | p[1][3] = 1 + above 0 + left 3 - overlap 0 = 4. The overlap was counted twice, so subtract it once. |
| 5 | L4 | r=1 c=4 g[r-1][c-1]=4 p[r][c]=8 | p[1][4] = 4 + above 0 + left 4 - overlap 0 = 8. The overlap was counted twice, so subtract it once. |
| 6 | L4 | r=2 c=1 g[r-1][c-1]=2 p[r][c]=5 | p[2][1] = 2 + above 3 + left 0 - overlap 0 = 5. The overlap was counted twice, so subtract it once. |
| 7 | L4 | r=2 c=2 g[r-1][c-1]=5 p[r][c]=10 | p[2][2] = 5 + above 3 + left 5 - overlap 3 = 10. The overlap was counted twice, so subtract it once. |
| 8 | L4 | r=2 c=3 g[r-1][c-1]=6 p[r][c]=17 | p[2][3] = 6 + above 4 + left 10 - overlap 3 = 17. The overlap was counted twice, so subtract it once. |
| 9 | L4 | r=2 c=4 g[r-1][c-1]=3 p[r][c]=24 | p[2][4] = 3 + above 8 + left 17 - overlap 4 = 24. The overlap was counted twice, so subtract it once. |
| 10 | L4 | r=3 c=1 g[r-1][c-1]=2 p[r][c]=7 | p[3][1] = 2 + above 5 + left 0 - overlap 0 = 7. The overlap was counted twice, so subtract it once. |
| 11 | L4 | r=3 c=2 g[r-1][c-1]=1 p[r][c]=13 | p[3][2] = 1 + above 10 + left 7 - overlap 5 = 13. The overlap was counted twice, so subtract it once. |
| 12 | L4 | r=3 c=3 g[r-1][c-1]=1 p[r][c]=21 | p[3][3] = 1 + above 17 + left 13 - overlap 10 = 21. The overlap was counted twice, so subtract it once. |
| 13 | L4 | r=3 c=4 g[r-1][c-1]=5 p[r][c]=33 | p[3][4] = 5 + above 24 + left 21 - overlap 17 = 33. The overlap was counted twice, so subtract it once. |
| 14 | L6 | query=(1,1) to (2,2) return=13 | Sum of the rectangle (1,1) to (2,2) = p[3][3] - p[1][3] - p[3][1] + p[1][1] = 21 - 4 - 7 + 3 = 13. Each query is O(1) after the O(R x C) build. |
Submatrix sums
The sum of the rectangle from (r1, c1) to (r2, c2), inclusive, is p[r2+1][c2+1] - p[r1][c2+1] - p[r2+1][c1] + p[r1][c1]: take the big block, remove the strip above and the strip to the left, and add back the corner that was removed twice. Each query is four lookups, O(1).
Choose the corner cells in the animation; the four used entries are highlighted.
1static long sum(long[][] p, int r1, int c1, int r2, int c2) { // inclusive corners2return p[r2 + 1][c2 + 1] - p[r1][c2 + 1] - p[r2 + 1][c1] + p[r1][c1];3}
2D prefix sums and submatrix queries
Interactive · edit the inputgrid · 4 × 5
The prefix matrix has one extra row and column of zeros, so every query formula works without special cases.
1int[][] p = new int[R + 1][C + 1]; // p[r][c] = sum of g[0..r-1][0..c-1]2for (int r = 1; r <= R; r++)3for (int c = 1; c <= C; c++)4p[r][c] = g[r - 1][c - 1] + p[r - 1][c] + p[r][c - 1] - p[r - 1][c - 1]; // inclusion-exclusion5static int sum(int[][] p, int r1, int c1, int r2, int c2) { // inclusive corners6return p[r2 + 1][c2 + 1] - p[r1][c2 + 1] - p[r2 + 1][c1] + p[r1][c1];7}
Live state
R
3
C
4
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | R=3 C=4 | The prefix matrix has one extra row and column of zeros, so every query formula works without special cases. |
| 2 | L4 | r=1 c=1 g[r-1][c-1]=3 p[r][c]=3 | p[1][1] = 3 + above 0 + left 0 - overlap 0 = 3. The overlap was counted twice, so subtract it once. |
| 3 | L4 | r=1 c=2 g[r-1][c-1]=0 p[r][c]=3 | p[1][2] = 0 + above 0 + left 3 - overlap 0 = 3. The overlap was counted twice, so subtract it once. |
| 4 | L4 | r=1 c=3 g[r-1][c-1]=1 p[r][c]=4 | p[1][3] = 1 + above 0 + left 3 - overlap 0 = 4. The overlap was counted twice, so subtract it once. |
| 5 | L4 | r=1 c=4 g[r-1][c-1]=4 p[r][c]=8 | p[1][4] = 4 + above 0 + left 4 - overlap 0 = 8. The overlap was counted twice, so subtract it once. |
| 6 | L4 | r=2 c=1 g[r-1][c-1]=2 p[r][c]=5 | p[2][1] = 2 + above 3 + left 0 - overlap 0 = 5. The overlap was counted twice, so subtract it once. |
| 7 | L4 | r=2 c=2 g[r-1][c-1]=5 p[r][c]=10 | p[2][2] = 5 + above 3 + left 5 - overlap 3 = 10. The overlap was counted twice, so subtract it once. |
| 8 | L4 | r=2 c=3 g[r-1][c-1]=6 p[r][c]=17 | p[2][3] = 6 + above 4 + left 10 - overlap 3 = 17. The overlap was counted twice, so subtract it once. |
| 9 | L4 | r=2 c=4 g[r-1][c-1]=3 p[r][c]=24 | p[2][4] = 3 + above 8 + left 17 - overlap 4 = 24. The overlap was counted twice, so subtract it once. |
| 10 | L4 | r=3 c=1 g[r-1][c-1]=2 p[r][c]=7 | p[3][1] = 2 + above 5 + left 0 - overlap 0 = 7. The overlap was counted twice, so subtract it once. |
| 11 | L4 | r=3 c=2 g[r-1][c-1]=1 p[r][c]=13 | p[3][2] = 1 + above 10 + left 7 - overlap 5 = 13. The overlap was counted twice, so subtract it once. |
| 12 | L4 | r=3 c=3 g[r-1][c-1]=1 p[r][c]=21 | p[3][3] = 1 + above 17 + left 13 - overlap 10 = 21. The overlap was counted twice, so subtract it once. |
| 13 | L4 | r=3 c=4 g[r-1][c-1]=5 p[r][c]=33 | p[3][4] = 5 + above 24 + left 21 - overlap 17 = 33. The overlap was counted twice, so subtract it once. |
| 14 | L6 | query=(1,1) to (2,2) return=13 | Sum of the rectangle (1,1) to (2,2) = p[3][3] - p[1][3] - p[3][1] + p[1][1] = 21 - 4 - 7 + 3 = 13. Each query is O(1) after the O(R x C) build. |
2D difference arrays
A 2D difference array is the inverse of a 2D prefix sum: d is chosen so that taking its 2D prefix sum yields the grid. Adding v to a whole rectangle then changes only four cells of d, and one prefix-sum pass at the end produces the final grid. It makes m updates cost O(m + R·C) instead of O(m·R·C).
Enter several updates as r1, c1, r2, c2, v and watch four corners change per update.
1int[][] d = new int[n + 1][n + 1];2for (int[] u : updates) { // u = {r1, c1, r2, c2, v}3d[u[0]][u[1]] += u[4];4d[u[0]][u[3] + 1] -= u[4];5d[u[2] + 1][u[1]] -= u[4];6d[u[2] + 1][u[3] + 1] += u[4];7}
2D difference array: rectangle updates
Interactive · edit the inputgrid · 4 × 4
The difference grid d is 4 x 4. Each rectangle update touches only four corners, however large the rectangle is.
1int[][] d = new int[n + 1][n + 1];2for (int[] u : updates) { // u = {r1, c1, r2, c2, v}3d[u[0]][u[1]] += u[4];4d[u[0]][u[3] + 1] -= u[4];5d[u[2] + 1][u[1]] -= u[4];6d[u[2] + 1][u[3] + 1] += u[4]; // four corners instead of the whole rectangle7}8// rebuild: a 2D prefix sum of d gives the final grid9for (int r = 0; r < n; r++) for (int c = 0; c < n; c++) {10g[r][c] = d[r][c] + (r > 0 ? g[r - 1][c] : 0) + (c > 0 ? g[r][c - 1] : 0) - (r > 0 && c > 0 ? g[r - 1][c - 1] : 0);11}
Live state
n
3
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=3 | The difference grid d is 4 x 4. Each rectangle update touches only four corners, however large the rectangle is. |
| 2 | L6 | update=(0,0)-(1,1) +2 | Add 2 to the rectangle (0,0) to (1,1): +2 at (0,0), -2 at (0,2), -2 at (2,0), +2 at (2,2). |
| 3 | L6 | update=(1,1)-(2,2) +3 | Add 3 to the rectangle (1,1) to (2,2): +3 at (1,1), -3 at (1,3), -3 at (3,1), +3 at (3,3). |
| 4 | L10 | r=0 c=0 g[r][c]=2 | g[0][0] = d[0][0] + above + left - overlap = 2. |
| 5 | L10 | r=0 c=1 g[r][c]=2 | g[0][1] = d[0][1] + above + left - overlap = 2. |
| 6 | L10 | r=0 c=2 g[r][c]=0 | g[0][2] = d[0][2] + above + left - overlap = 0. |
| 7 | L10 | r=1 c=0 g[r][c]=2 | g[1][0] = d[1][0] + above + left - overlap = 2. |
| 8 | L10 | r=1 c=1 g[r][c]=5 | g[1][1] = d[1][1] + above + left - overlap = 5. |
| 9 | L10 | r=1 c=2 g[r][c]=3 | g[1][2] = d[1][2] + above + left - overlap = 3. |
| 10 | L10 | r=2 c=0 g[r][c]=0 | g[2][0] = d[2][0] + above + left - overlap = 0. |
| 11 | L10 | r=2 c=1 g[r][c]=3 | g[2][1] = d[2][1] + above + left - overlap = 3. |
| 12 | L10 | r=2 c=2 g[r][c]=3 | g[2][2] = d[2][2] + above + left - overlap = 3. |
| 13 | L11 | return=[[2, 2, 0], [2, 5, 3], [0, 3, 3]] | 2 updates applied in O(1) each plus one O(n²) rebuild, instead of O(n²) per update. |
Rectangular range updates
To rebuild after the updates, take the 2D prefix sum of d: g[r][c] = d[r][c] + g[r-1][c] + g[r][c-1] - g[r-1][c-1]. Each corner edit propagates over the whole rectangle to its lower right and is cancelled by the opposite corners at the rectangle's edge. This is the 2D version of the Imos method.
Overlapping rectangles simply add.
1int[][] g = new int[n][n];2for (int r = 0; r < n; r++)3for (int c = 0; c < n; c++)4g[r][c] = d[r][c] + (r > 0 ? g[r - 1][c] : 0) + (c > 0 ? g[r][c - 1] : 0) - (r > 0 && c > 0 ? g[r - 1][c - 1] : 0);
2D difference array: rectangle updates
Interactive · edit the inputgrid · 4 × 4
The difference grid d is 4 x 4. Each rectangle update touches only four corners, however large the rectangle is.
1int[][] d = new int[n + 1][n + 1];2for (int[] u : updates) { // u = {r1, c1, r2, c2, v}3d[u[0]][u[1]] += u[4];4d[u[0]][u[3] + 1] -= u[4];5d[u[2] + 1][u[1]] -= u[4];6d[u[2] + 1][u[3] + 1] += u[4]; // four corners instead of the whole rectangle7}8// rebuild: a 2D prefix sum of d gives the final grid9for (int r = 0; r < n; r++) for (int c = 0; c < n; c++) {10g[r][c] = d[r][c] + (r > 0 ? g[r - 1][c] : 0) + (c > 0 ? g[r][c - 1] : 0) - (r > 0 && c > 0 ? g[r - 1][c - 1] : 0);11}
Live state
n
3
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=3 | The difference grid d is 4 x 4. Each rectangle update touches only four corners, however large the rectangle is. |
| 2 | L6 | update=(0,0)-(1,1) +2 | Add 2 to the rectangle (0,0) to (1,1): +2 at (0,0), -2 at (0,2), -2 at (2,0), +2 at (2,2). |
| 3 | L6 | update=(1,1)-(2,2) +3 | Add 3 to the rectangle (1,1) to (2,2): +3 at (1,1), -3 at (1,3), -3 at (3,1), +3 at (3,3). |
| 4 | L10 | r=0 c=0 g[r][c]=2 | g[0][0] = d[0][0] + above + left - overlap = 2. |
| 5 | L10 | r=0 c=1 g[r][c]=2 | g[0][1] = d[0][1] + above + left - overlap = 2. |
| 6 | L10 | r=0 c=2 g[r][c]=0 | g[0][2] = d[0][2] + above + left - overlap = 0. |
| 7 | L10 | r=1 c=0 g[r][c]=2 | g[1][0] = d[1][0] + above + left - overlap = 2. |
| 8 | L10 | r=1 c=1 g[r][c]=5 | g[1][1] = d[1][1] + above + left - overlap = 5. |
| 9 | L10 | r=1 c=2 g[r][c]=3 | g[1][2] = d[1][2] + above + left - overlap = 3. |
| 10 | L10 | r=2 c=0 g[r][c]=0 | g[2][0] = d[2][0] + above + left - overlap = 0. |
| 11 | L10 | r=2 c=1 g[r][c]=3 | g[2][1] = d[2][1] + above + left - overlap = 3. |
| 12 | L10 | r=2 c=2 g[r][c]=3 | g[2][2] = d[2][2] + above + left - overlap = 3. |
| 13 | L11 | return=[[2, 2, 0], [2, 5, 3], [0, 3, 3]] | 2 updates applied in O(1) each plus one O(n²) rebuild, instead of O(n²) per update. |
Practice ladder
Now try it yourself
Easy
- Build the prefix table of [[1, 2], [3, 4]]
- How many table lookups does one submatrix query need?
Medium
- Find the sum of the rectangle from (1, 1) to (2, 2) of a 3 × 3 grid using prefix sums
- Apply two overlapping rectangle updates with a difference array
Advanced
- Find the maximum-sum k × k square in O(R·C) using 2D prefix sums
Key takeaways
- p[r][c] = cell + above + left - overlap; a query is p(big) - p(top) - p(left) + p(corner).
- Rectangle updates touch four corners; one prefix-sum pass rebuilds the grid.
- Use long for the sums when values or sizes are large.