A 2-D prefix sum is the matrix version of the running-total trick: precompute one table and then read the sum of any axis-aligned rectangle in constant time with four lookups. It turns "what is the total of this sub-block?" from an O(m·n)-per-query scan into O(1).
Core idea. Build a padded table pre where pre[i][j] holds the sum of every grid cell strictly
above row i and strictly left of column j — the whole rectangle from the top-left corner up to that
point. Then the sum of the block with corners (r1,c1) and (r2,c2) is four corner reads:
pre[r2+1][c2+1] - pre[r1][c2+1] - pre[r2+1][c1] + pre[r1][c1].
For the grid [[3, 0, 1], [2, 1, 4], [1, 5, 1]], the sum of the rectangle from (0,1) to (1,2) is 0 + 1 + 1 + 4 = 6. We will read exactly that with the four-corner formula.
Intuition
Think of pre[i][j] as "how much money is in the whole rectangle from the top-left corner of the grid down to here?" Once you have those running totals, any sub-rectangle is the big rectangle minus the parts you do not want.
Imagine cutting out a rectangle from inside a larger one. Start with the big block that ends at the bottom-right corner. Subtract the strip sitting above your rectangle and the strip sitting to its left. But those two strips overlap in one square — the top-left corner block — and you just removed it twice, so you add it back once. That add-back is the classic inclusion-exclusion move, and it is the only subtle part of the whole technique.
Building the table uses the very same idea in reverse. Each new cell is its own grid value plus the cell above plus the cell to the left, minus the top-left overlap that those two share.
Walk through it
Step through the animation on the right. The left grid is the input; the right grid is the padded pre table, which has an extra zero row on top and an extra zero column on the left so every neighbour read is in bounds.
Phase 1 — build. The fill pointer sweeps the table cell by cell, row by row. For each target cell, three contributing neighbours light up: the cell up and the cell left are added (shown as one color), and the up-left overlap is subtracted (a second color) because it is double-counted by the other two. Watch pre[2][2] get written as grid[1][1] + up + left - upLeft.
Phase 2 — query. Now we answer the rectangle (0,1)..(1,2). The four corners of the pre table light up with their inclusion-exclusion signs: the big block pre[2][3] is +, the strip-above pre[0][3] and strip-left pre[2][1] are both -, and the doubly-removed corner pre[0][1] is +. Add them with their signs and the rectangle sum drops out: 8 - 1 - 3 + 2 = 6.
The code, line by line
def build_prefix(grid):
m, n = len(grid), len(grid[0])
pre = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m):
for j in range(n):
pre[i + 1][j + 1] = (grid[i][j]
+ pre[i][j + 1] + pre[i + 1][j]
- pre[i][j])
return pre
def rect_sum(pre, r1, c1, r2, c2):
return (pre[r2 + 1][c2 + 1]
- pre[r1][c2 + 1]
- pre[r2 + 1][c1]
+ pre[r1][c1])- Line 3 allocates the
(m+1)×(n+1)table filled with zeros. That extra zero row and zero column are the padding — they makepre[i][j+1],pre[i+1][j], andpre[i][j]always valid, even on the first row and column. - Lines 6–8 are the build recurrence: each cell is its grid value plus the cell directly above plus the cell directly to the left, minus the top-left overlap that those two both include.
- Line 12 reads the big block
pre[r2+1][c2+1]— everything from the grid origin to the bottom-right corner of the query. - Lines 13–14 subtract the strip above and the strip to the left of the rectangle.
- Line 15 adds back
pre[r1][c1], the top-left corner block that both strips removed, fixing the double subtraction.
Complexity
| Case | Time | Notes |
|---|---|---|
| Build | O(m*n) (moderate) | one sweep over the grid to fill the table |
| Query | O(1) (fast) | four corner lookups per rectangle, any size |
| Space | O(m*n) (moderate) | the padded (m+1)x(n+1) prefix table |
O(m*n) (moderate)The win is amortized over many queries: you pay O(m*n) once to build, then every rectangle-sum question — no matter how big the rectangle — costs four reads. With q queries the total is O(m*n + q) instead of O(q * m * n) for repeated brute-force scans.
When to use / pitfalls
Reach for a 2-D prefix sum whenever a problem asks for the sum of many sub-rectangles of a fixed matrix (Range Sum Query 2D, Matrix Block Sum, counting submatrices that sum to a target). The signal: the grid does not change between queries, and a naive per-query scan would be O(m*n). The same padding-plus-four- corners pattern also extends to counting and to 3-D volumes.
Two classic mistakes. First, off-by-one with the padding: pre is indexed one larger than grid, so
the block ending at grid cell (r2,c2) is pre[r2+1][c2+1] — forget the +1 and you drop a row and a
column. Second, forgetting to add back the corner: subtracting both the top strip and the left strip
removes their shared top-left block twice, so you must add pre[r1][c1] back exactly once. Skipping it
undercounts every query.
Practice
For grid = [[3, 0, 1], [2, 1, 4], [1, 5, 1]], what is the sum of the rectangle from (0,1) to (1,2) using the four-corner formula?
1. Why is the prefix table sized (m+1) x (n+1) instead of m x n?
2. In the build recurrence, why subtract pre[i][j]?
3. What is the sum of the rectangle from (0,1) to (1,2) for grid = [[3, 0, 1], [2, 1, 4], [1, 5, 1]]?
4. After building the table once, what is the cost of one rectangle-sum query?