304. Range Sum Query 2D - Immutable
Problem Description
You are given a 2D matrix of integers and need to efficiently calculate the sum of elements within any rectangular region of this matrix.
The task requires implementing a NumMatrix
class with two methods:
-
Constructor
NumMatrix(int[][] matrix)
: Takes a 2D integer matrix as input and initializes the data structure. -
Query Method
sumRegion(int row1, int col1, int row2, int col2)
: Returns the sum of all elements within the rectangle defined by:- Upper left corner at position
(row1, col1)
- Lower right corner at position
(row2, col2)
- Both corners are inclusive in the sum calculation
- Upper left corner at position
Key Constraint: The sumRegion
method must operate in O(1)
time complexity, meaning each query should return the result in constant time regardless of the rectangle size.
The solution uses a 2D prefix sum approach. A prefix sum array s[i+1][j+1]
stores the sum of all elements from the top-left corner (0, 0)
to position (i, j)
in the original matrix.
During initialization, the prefix sum array is built using the formula:
s[i+1][j+1] = s[i][j+1] + s[i+1][j] - s[i][j] + matrix[i][j]
For any query, the sum of a rectangle can be calculated in constant time using:
sum = s[row2+1][col2+1] - s[row2+1][col1] - s[row1][col2+1] + s[row1][col1]
This formula works by taking the total sum up to the bottom-right corner, then subtracting the regions outside the desired rectangle (left and top portions), and adding back the overlapping corner that was subtracted twice.
Intuition
The naive approach would be to iterate through all elements in the rectangle for each query, but this would take O(m*n)
time per query where m
and n
are the dimensions of the rectangle. Since we need O(1)
query time, we need to do some preprocessing.
Think about how we handle range sum queries in a 1D array - we use prefix sums where prefix[i]
stores the sum of elements from index 0 to i. Then any range sum from index i
to j
can be calculated as prefix[j] - prefix[i-1]
in constant time.
Can we extend this idea to 2D? Yes! We can create a 2D prefix sum where each cell stores the sum of all elements in the rectangle from (0,0)
to that cell.
To visualize this, imagine you want the sum of a rectangle. If you have the sum of the entire region from origin to the bottom-right corner of your target rectangle, you've included too much. You need to subtract the regions to the left and above your target rectangle. But wait - when you subtract both these regions, you've subtracted their overlap twice! So you need to add it back once.
This gives us the inclusion-exclusion principle for 2D:
- Start with the sum from origin to bottom-right:
s[row2+1][col2+1]
- Subtract the left excess:
- s[row2+1][col1]
- Subtract the top excess:
- s[row1][col2+1]
- Add back the overlap:
+ s[row1][col1]
Building the prefix sum array follows similar logic. To calculate the sum up to position (i,j)
, we take the sum up to (i-1,j)
and (i,j-1)
, subtract their overlap (i-1,j-1)
(which was counted twice), and add the current element matrix[i][j]
.
This preprocessing takes O(m*n)
time once, but then every query is just four array lookups - achieving our O(1)
goal.
Solution Approach
The solution implements a 2D prefix sum array to achieve constant-time rectangle sum queries.
Data Structure:
- Create a 2D array
s
with dimensions(m+1) x (n+1)
wherem
andn
are the rows and columns of the original matrix - The extra row and column (filled with zeros) simplify boundary handling by eliminating the need for special cases
Initialization Phase:
During construction, we build the prefix sum array where s[i+1][j+1]
represents the sum of all elements in the rectangle from (0,0)
to (i,j)
in the original matrix.
For each cell, we calculate:
s[i+1][j+1] = s[i][j+1] + s[i+1][j] - s[i][j] + matrix[i][j]
Breaking down this formula:
s[i][j+1]
: sum of rectangle from(0,0)
to(i-1,j)
s[i+1][j]
: sum of rectangle from(0,0)
to(i,j-1)
s[i][j]
: sum of rectangle from(0,0)
to(i-1,j-1)
(subtracted because it's counted twice)matrix[i][j]
: current element value
Query Phase:
To find the sum of rectangle from (row1, col1)
to (row2, col2)
, we use:
sum = s[row2+1][col2+1] - s[row2+1][col1] - s[row1][col2+1] + s[row1][col1]
This applies the inclusion-exclusion principle:
s[row2+1][col2+1]
: total sum from origin to(row2, col2)
-s[row2+1][col1]
: subtract the left region outside our target rectangle-s[row1][col2+1]
: subtract the top region outside our target rectangle+s[row1][col1]
: add back the top-left region that was subtracted twice
Time Complexity:
- Initialization:
O(m*n)
to build the prefix sum array - Query:
O(1)
as it only involves four array lookups and arithmetic operations
Space Complexity: O(m*n)
for storing the prefix sum array
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Given matrix:
[3, 0, 1] [5, 6, 3] [1, 2, 0]
Step 1: Build the Prefix Sum Array
We create a prefix sum array s
with dimensions (4 x 4) - one row and column larger than the original matrix. The first row and column are initialized with zeros.
Starting with:
s = [0, 0, 0, 0] [0, ?, ?, ?] [0, ?, ?, ?] [0, ?, ?, ?]
Now we fill each cell using the formula: s[i+1][j+1] = s[i][j+1] + s[i+1][j] - s[i][j] + matrix[i][j]
For position (0,0) in matrix (value = 3):
s[1][1] = s[0][1] + s[1][0] - s[0][0] + 3 = 0 + 0 - 0 + 3 = 3
For position (0,1) in matrix (value = 0):
s[1][2] = s[0][2] + s[1][1] - s[0][1] + 0 = 0 + 3 - 0 + 0 = 3
For position (0,2) in matrix (value = 1):
s[1][3] = s[0][3] + s[1][2] - s[0][2] + 1 = 0 + 3 - 0 + 1 = 4
Continuing this process for all cells, we get:
s = [0, 0, 0, 0] [0, 3, 3, 4] [0, 8, 14, 18] [0, 9, 17, 21]
Each cell s[i][j]
contains the sum of all elements from (0,0) to (i-1,j-1) in the original matrix.
Step 2: Query Example - Sum of Rectangle from (1,1) to (2,2)
We want to find the sum of:
[6, 3] [2, 0]
Using our formula: sum = s[row2+1][col2+1] - s[row2+1][col1] - s[row1][col2+1] + s[row1][col1]
Substituting values:
row1 = 1, col1 = 1, row2 = 2, col2 = 2
sum = s[3][3] - s[3][1] - s[1][3] + s[1][1]
sum = 21 - 9 - 4 + 3
sum = 11
Let's verify: 6 + 3 + 2 + 0 = 11 ✓
Visual Understanding:
The calculation works because:
s[3][3] = 21
gives us everything from (0,0) to (2,2)- We subtract
s[3][1] = 9
to remove the left column (values 3, 5, 1) - We subtract
s[1][3] = 4
to remove the top row (values 3, 0, 1) - We add back
s[1][1] = 3
because we subtracted the top-left corner (value 3) twice
This demonstrates how the 2D prefix sum enables constant-time rectangle sum queries after the initial preprocessing.
Solution Implementation
1from typing import List
2
3
4class NumMatrix:
5 def __init__(self, matrix: List[List[int]]):
6 """
7 Initialize the data structure with a 2D matrix.
8 Build a 2D prefix sum array for efficient range sum queries.
9
10 Args:
11 matrix: 2D list of integers
12 """
13 rows, cols = len(matrix), len(matrix[0])
14
15 # Create prefix sum array with extra row and column of zeros for easier calculation
16 # prefix_sum[i][j] represents the sum of all elements from (0,0) to (i-1,j-1)
17 self.prefix_sum = [[0] * (cols + 1) for _ in range(rows + 1)]
18
19 # Build the prefix sum array
20 for i, row in enumerate(matrix):
21 for j, value in enumerate(row):
22 # Current prefix sum = sum above + sum to the left - overlap + current value
23 self.prefix_sum[i + 1][j + 1] = (
24 self.prefix_sum[i][j + 1] + # Sum of rectangle above
25 self.prefix_sum[i + 1][j] - # Sum of rectangle to the left
26 self.prefix_sum[i][j] + # Remove double-counted overlap
27 value # Add current cell value
28 )
29
30 def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
31 """
32 Calculate the sum of elements in the rectangle defined by its corners.
33
34 Args:
35 row1: Top row index (inclusive)
36 col1: Left column index (inclusive)
37 row2: Bottom row index (inclusive)
38 col2: Right column index (inclusive)
39
40 Returns:
41 Sum of all elements in the specified rectangle
42 """
43 # Use inclusion-exclusion principle to get the rectangle sum
44 return (
45 self.prefix_sum[row2 + 1][col2 + 1] - # Total sum from origin to bottom-right
46 self.prefix_sum[row2 + 1][col1] - # Subtract left rectangle
47 self.prefix_sum[row1][col2 + 1] + # Subtract top rectangle
48 self.prefix_sum[row1][col1] # Add back double-subtracted overlap
49 )
50
51
52# Your NumMatrix object will be instantiated and called as such:
53# obj = NumMatrix(matrix)
54# param_1 = obj.sumRegion(row1, col1, row2, col2)
55
1class NumMatrix {
2 // 2D prefix sum array where prefixSum[i][j] represents the sum of all elements
3 // in the rectangle from (0, 0) to (i-1, j-1)
4 private int[][] prefixSum;
5
6 public NumMatrix(int[][] matrix) {
7 int rows = matrix.length;
8 int cols = matrix[0].length;
9
10 // Initialize prefix sum array with an extra row and column of zeros
11 // This helps avoid boundary checks when calculating sums
12 prefixSum = new int[rows + 1][cols + 1];
13
14 // Build the 2D prefix sum array
15 for (int i = 0; i < rows; ++i) {
16 for (int j = 0; j < cols; ++j) {
17 // Calculate prefix sum using the inclusion-exclusion principle:
18 // prefixSum[i+1][j+1] = sum of rectangle from (0,0) to (i,j)
19 // = prefixSum of left rectangle + prefixSum of top rectangle
20 // - prefixSum of overlapping top-left rectangle + current element
21 prefixSum[i + 1][j + 1] = prefixSum[i + 1][j] // left rectangle
22 + prefixSum[i][j + 1] // top rectangle
23 - prefixSum[i][j] // overlap (subtract once)
24 + matrix[i][j]; // current element
25 }
26 }
27 }
28
29 public int sumRegion(int row1, int col1, int row2, int col2) {
30 // Calculate the sum of the rectangle from (row1, col1) to (row2, col2)
31 // using the inclusion-exclusion principle:
32 // Sum = total rectangle from origin to (row2, col2)
33 // - rectangle from origin to (row2, col1-1)
34 // - rectangle from origin to (row1-1, col2)
35 // + rectangle from origin to (row1-1, col1-1) (added back as it was subtracted twice)
36 return prefixSum[row2 + 1][col2 + 1] // total rectangle
37 - prefixSum[row2 + 1][col1] // subtract left part
38 - prefixSum[row1][col2 + 1] // subtract top part
39 + prefixSum[row1][col1]; // add back overlapping part
40 }
41}
42
43/**
44 * Your NumMatrix object will be instantiated and called as such:
45 * NumMatrix obj = new NumMatrix(matrix);
46 * int param_1 = obj.sumRegion(row1,col1,row2,col2);
47 */
48
1class NumMatrix {
2public:
3 // 2D prefix sum array where prefixSum[i][j] represents the sum of all elements
4 // in the rectangle from (0, 0) to (i-1, j-1)
5 vector<vector<int>> prefixSum;
6
7 NumMatrix(vector<vector<int>>& matrix) {
8 int rows = matrix.size();
9 int cols = matrix[0].size();
10
11 // Initialize prefix sum array with dimensions (rows + 1) x (cols + 1)
12 // Extra row and column of zeros simplify boundary conditions
13 prefixSum.resize(rows + 1, vector<int>(cols + 1, 0));
14
15 // Build the 2D prefix sum array
16 for (int i = 0; i < rows; ++i) {
17 for (int j = 0; j < cols; ++j) {
18 // Formula: prefixSum[i+1][j+1] = sum of rectangle from (0,0) to (i,j)
19 // = sum to left + sum above - overlap + current element
20 prefixSum[i + 1][j + 1] = prefixSum[i + 1][j] // Sum of elements to the left
21 + prefixSum[i][j + 1] // Sum of elements above
22 - prefixSum[i][j] // Subtract overlapping area (included twice)
23 + matrix[i][j]; // Add current element
24 }
25 }
26 }
27
28 int sumRegion(int row1, int col1, int row2, int col2) {
29 // Calculate sum of rectangle from (row1, col1) to (row2, col2) using inclusion-exclusion principle
30 // Total area = entire rectangle - left rectangle - top rectangle + top-left overlap
31 return prefixSum[row2 + 1][col2 + 1] // Sum from (0,0) to (row2, col2)
32 - prefixSum[row2 + 1][col1] // Subtract sum from (0,0) to (row2, col1-1)
33 - prefixSum[row1][col2 + 1] // Subtract sum from (0,0) to (row1-1, col2)
34 + prefixSum[row1][col1]; // Add back sum from (0,0) to (row1-1, col1-1) (subtracted twice)
35 }
36};
37
38/**
39 * Your NumMatrix object will be instantiated and called as such:
40 * NumMatrix* obj = new NumMatrix(matrix);
41 * int param_1 = obj->sumRegion(row1,col1,row2,col2);
42 */
43
1// 2D prefix sum array for storing cumulative sums
2let prefixSum: number[][];
3
4/**
5 * Initializes the data structure with the given matrix.
6 * Builds a 2D prefix sum array where prefixSum[i][j] represents
7 * the sum of all elements in the rectangle from (0,0) to (i-1,j-1)
8 * @param matrix - The input 2D matrix of integers
9 */
10function NumMatrix(matrix: number[][]): void {
11 const rows = matrix.length;
12 const cols = matrix[0].length;
13
14 // Initialize prefix sum array with dimensions (rows+1) x (cols+1)
15 // Extra row and column of zeros simplify boundary calculations
16 prefixSum = Array(rows + 1).fill(0).map(() => Array(cols + 1).fill(0));
17
18 // Build the prefix sum array
19 for (let i = 0; i < rows; i++) {
20 for (let j = 0; j < cols; j++) {
21 // Current prefix sum = sum to the left + sum above - overlap + current element
22 prefixSum[i + 1][j + 1] =
23 prefixSum[i + 1][j] + // Sum of elements to the left
24 prefixSum[i][j + 1] - // Sum of elements above
25 prefixSum[i][j] + // Remove double-counted overlap
26 matrix[i][j]; // Add current element
27 }
28 }
29}
30
31/**
32 * Calculates the sum of elements in the rectangle defined by
33 * (row1, col1) as top-left corner and (row2, col2) as bottom-right corner
34 * @param row1 - Top row index (inclusive)
35 * @param col1 - Left column index (inclusive)
36 * @param row2 - Bottom row index (inclusive)
37 * @param col2 - Right column index (inclusive)
38 * @returns The sum of all elements in the specified rectangle
39 */
40function sumRegion(row1: number, col1: number, row2: number, col2: number): number {
41 // Use inclusion-exclusion principle to calculate rectangle sum
42 return prefixSum[row2 + 1][col2 + 1] - // Total sum to bottom-right
43 prefixSum[row2 + 1][col1] - // Subtract left portion
44 prefixSum[row1][col2 + 1] + // Subtract top portion
45 prefixSum[row1][col1]; // Add back double-subtracted overlap
46}
47
Time and Space Complexity
Time Complexity:
-
Initialization (
__init__
):O(m × n)
, wherem
is the number of rows andn
is the number of columns in the input matrix. The algorithm iterates through each element of the matrix exactly once to build the 2D prefix sum array. Each cell computation involves constant time operations (addition and subtraction). -
Query (
sumRegion
):O(1)
. The method performs exactly four array lookups and three arithmetic operations (two subtractions and one addition), all of which take constant time regardless of the size of the queried region.
Space Complexity:
O(m × n)
. The algorithm creates a 2D prefix sum arrayself.s
with dimensions(m + 1) × (n + 1)
to store the cumulative sums. This auxiliary array requires space proportional to the size of the input matrix. The extra row and column (the+1
in dimensions) are used as padding to simplify boundary conditions but don't change the overall space complexity class.
The prefix sum approach trades additional space for efficient query performance, making it ideal for scenarios with multiple range sum queries on the same matrix.
Common Pitfalls
1. Off-by-One Errors in Index Mapping
The most common mistake is confusion between the original matrix indices and the prefix sum array indices. Since the prefix sum array has an extra row and column (padding with zeros), there's a shift in indexing:
prefix_sum[i+1][j+1]
corresponds to the sum from(0,0)
to(i,j)
in the original matrix- Many developers accidentally use
prefix_sum[i][j]
for position(i,j)
, leading to incorrect results
Solution: Always remember the +1
offset when accessing the prefix sum array. Think of prefix_sum[i][j]
as "the sum up to but not including row i and column j."
2. Incorrect Formula Application During Initialization
When building the prefix sum, developers often write:
# WRONG - Missing the overlap subtraction self.prefix_sum[i+1][j+1] = ( self.prefix_sum[i][j+1] + self.prefix_sum[i+1][j] + matrix[i][j] )
This double-counts the rectangle from (0,0)
to (i-1,j-1)
, resulting in inflated sums.
Solution: Always subtract the overlap: - self.prefix_sum[i][j]
3. Forgetting to Handle Empty Matrix Edge Cases
If not careful with initialization, the code might crash on empty matrices or matrices with zero rows/columns:
# This would fail if matrix is empty
rows, cols = len(matrix), len(matrix[0]) # IndexError if matrix is []
Solution: Add validation at the beginning:
if not matrix or not matrix[0]: self.prefix_sum = [[0]] return
4. Incorrect Rectangle Sum Formula Signs
A frequent error is mixing up the signs in the query formula:
# WRONG - Incorrect signs return ( self.prefix_sum[row2 + 1][col2 + 1] + # Should be + self.prefix_sum[row2 + 1][col1] - # Should be - self.prefix_sum[row1][col2 + 1] - # Should be - self.prefix_sum[row1][col1] # Should be + )
Solution: Remember the pattern: main rectangle (+), subtract left (-), subtract top (-), add back overlap (+). Visualizing the rectangles being added/subtracted helps prevent sign errors.
5. Not Understanding Why the Extra Row/Column is Needed
Some developers try to optimize by removing the padding:
# WRONG - Creates complex boundary conditions
self.prefix_sum = [[0] * cols for _ in range(rows)]
This requires special handling when row1 = 0
or col1 = 0
, making the code error-prone.
Solution: Keep the padding. The small space overhead eliminates all boundary condition checks, making the code cleaner and less bug-prone.
Which two pointer techniques do you use to check if a string is a palindrome?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!