The problem
You are given a matrix of integers that never changes. Build a class that answers many queries sumRegion(r1, c1, r2, c2): the sum of every cell in the rectangle with top-left corner (r1, c1) and bottom-right corner (r2, c2), inclusive.
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) -> 8Rows 2 to 4, columns 1 to 3 are 2 0 1 / 1 0 1 / 0 3 0, which add up to 8.
How to think about it
Start with brute force. Loop over every cell in the rectangle per query: O(R·C) each. Slightly better, keep a 1-D prefix sum per row and add R row-ranges per query: O(R) each. With 10⁴ queries both are wasteful.
The key insight: extend prefix sums to two dimensions. Let pre[r][c] be the sum of the rectangle from the origin (0, 0) up to, but not including, row r and column c. Any rectangle is then the big origin rectangle, minus the strip above, minus the strip to the left, plus the top-left corner you subtracted twice. That is inclusion–exclusion, and it is four lookups: O(1).
Building it uses the same idea in reverse. pre[r+1][c+1] = the cell itself + the block above + the block to the left − the overlap of those two (counted twice). One pass over the matrix fills the table. The extra row and column of zeros, as in 1-D, remove all the boundary special cases.
Which pattern is this? Prefix sums, 2-D version. The tell is “many rectangle-sum queries on a fixed grid”.
Approach
- Create
preof size(R+1) × (C+1), all zeros. - For each cell, set
pre[r+1][c+1] = m[r][c] + pre[r][c+1] + pre[r+1][c] - pre[r][c]. - For a query, return
pre[r2+1][c2+1] - pre[r1][c2+1] - pre[r2+1][c1] + pre[r1][c1].
Solution
class NumMatrix:
def __init__(self, m):
R, C = len(m), len(m[0])
self.pre = [[0]*(C+1) for _ in range(R+1)] # padded with a zero row and column
for r in range(R):
for c in range(C):
# cell + block above + block left - overlap counted twice
self.pre[r+1][c+1] = m[r][c] + self.pre[r][c+1] + self.pre[r+1][c] - self.pre[r][c]
def sumRegion(self, r1, c1, r2, c2):
p = self.pre
# whole - strip above - strip left + corner subtracted twice
return p[r2+1][c2+1] - p[r1][c2+1] - p[r2+1][c1] + p[r1][c1]
# nm = NumMatrix([[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]])
# nm.sumRegion(2,1,4,3) -> 8Dry run
The built table pre (row 0 and column 0 are the zero padding):
pre row |
values |
|---|---|
| 0 | 0, 0, 0, 0, 0, 0 |
| 1 | 0, 3, 3, 4, 8, 10 |
| 2 | 0, 8, 14, 18, 24, 27 |
| 3 | 0, 9, 17, 21, 28, 36 |
| 4 | 0, 13, 22, 26, 34, 49 |
| 5 | 0, 14, 23, 30, 38, 58 |
For sumRegion(2, 1, 4, 3):
| Term | Meaning | Value |
|---|---|---|
p[5][4] |
origin to (4, 3) | 38 |
p[2][4] |
rows 0–1, cols 0–3 (strip above) | 24 |
p[5][1] |
rows 0–4, col 0 (strip left) | 14 |
p[2][1] |
rows 0–1, col 0 (corner, added back) | 8 |
38 − 24 − 14 + 8 = 8.
Complexity
- Time: O(R·C) to build, O(1) per query. Each cell is computed once from three neighbours; a query is four lookups.
- Space: O(R·C). The padded table has
(R+1)(C+1)entries.
Edge cases and pitfalls
- Forgetting to add the corner back gives an answer that is too small by
p[r1][c1]. - Index shift:
preis offset by one, so the query usesr2+1andc2+1for the far corner but plainr1andc1for the near edges. - Single-cell query (
r1 == r2,c1 == c2) returns just that cell’s value. - Empty matrix:
m[0]would fail. The constraints say the matrix is at least 1×1; guard it if they do not. - Mutable matrix (LeetCode 308) breaks this; it needs a 2-D Fenwick tree.
Related problems
- Range Sum Query - Immutable (LeetCode 303): the 1-D version of the same idea.
- Minimum Path Sum (LeetCode 64): another grid table filled from the cell above and the cell to the left.
- Subarray Sum Equals K (LeetCode 560): prefix sums combined with a hash map for counting.
- 1.Range Sum Query - Immutable (LeetCode 303)
- 2.Find Pivot Index (LeetCode 724)
- 3.Minimum Value to Get Positive Step by Step Sum (LeetCode 1413)
- 4.Subarray Sum Equals K (LeetCode 560)
- 5.Product of Array Except Self (LeetCode 238)
- 6.Contiguous Array (LeetCode 525)
- 7.Subarray Sums Divisible by K (LeetCode 974)
- 8.Continuous Subarray Sum (LeetCode 523)
- 9.Range Sum Query 2D - Immutable (LeetCode 304)
- 10.Maximum Size Subarray Sum Equals k (LeetCode 325)