Facebook Pixel

2536. Increment Submatrices by One

Problem Description

You start with an n x n matrix filled with zeros. You're given a list of queries, where each query specifies a rectangular region in the matrix. For each query, you need to add 1 to every element within that rectangular region.

Each query is defined by four values: [row1, col1, row2, col2], where:

  • (row1, col1) is the top-left corner of the rectangle
  • (row2, col2) is the bottom-right corner of the rectangle

For each query, you add 1 to all elements mat[x][y] where row1 ≤ x ≤ row2 and col1 ≤ y ≤ col2.

After processing all queries, return the final matrix.

The solution uses a 2D difference array technique to efficiently handle range updates:

  1. Marking boundaries: Instead of updating every element in each rectangular region (which would be inefficient), the solution marks the boundaries of each update region:

    • Add 1 at the top-left corner (x1, y1)
    • Subtract 1 at (x2+1, y1) to mark where the effect stops vertically
    • Subtract 1 at (x1, y2+1) to mark where the effect stops horizontally
    • Add 1 at (x2+1, y2+1) to correct the double subtraction
  2. Prefix sum reconstruction: After marking all boundaries, the solution uses 2D prefix sums to reconstruct the final matrix:

    • For each cell, accumulate values from the cell above (mat[i-1][j])
    • Add values from the cell to the left (mat[i][j-1])
    • Subtract the diagonal overlap (mat[i-1][j-1]) to avoid double counting

This approach reduces the time complexity from O(queries × area) to O(n² + queries), making it much more efficient for multiple queries on large regions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The naive approach would be to iterate through each query and add 1 to every cell in the specified rectangle. However, this becomes inefficient when we have many queries or large rectangles, potentially resulting in O(queries × n²) time complexity.

The key insight is recognizing that we can defer the actual updates and instead mark only the boundaries of where changes begin and end. Think of it like turning on and off water taps - instead of manually filling every cup in a region, we can set up a system where water flows automatically to the right areas.

Consider what happens when we want to add 1 to a rectangle. If we think in 1D first: to add 1 to elements from index i to j, we can mark +1 at position i and -1 at position j+1. Then, when we compute the prefix sum, all elements between i and j will naturally get the +1 value.

Extending this to 2D, we need to mark four corners:

  • +1 at (row1, col1): This starts the addition effect
  • -1 at (row2+1, col1): This stops the effect from flowing beyond row2
  • -1 at (row1, col2+1): This stops the effect from flowing beyond col2
  • +1 at (row2+1, col2+1): This corrects for the double subtraction that would occur when both row and column boundaries overlap

The magic happens in the reconstruction phase. When we compute 2D prefix sums, each cell accumulates all the boundary markers that affect it. The formula mat[i][j] += mat[i-1][j] + mat[i][j-1] - mat[i-1][j-1] essentially "spreads" the effects of all boundary markers across the matrix, automatically applying the correct increment to each cell based on which rectangles it belongs to.

This transforms our problem from "update many cells multiple times" to "mark a few boundaries once, then reconstruct the matrix once", dramatically improving efficiency.

Learn more about Prefix Sum patterns.

Solution Approach

The solution implements a 2D difference array technique combined with 2D prefix sum reconstruction. Here's how the implementation works:

Step 1: Initialize the Matrix

mat = [[0] * n for _ in range(n)]

Create an n × n matrix filled with zeros. This will serve as our difference array where we'll mark boundaries.

Step 2: Mark Boundaries for Each Query

for x1, y1, x2, y2 in queries:
    mat[x1][y1] += 1
    if x2 + 1 < n:
        mat[x2 + 1][y1] -= 1
    if y2 + 1 < n:
        mat[x1][y2 + 1] -= 1
    if x2 + 1 < n and y2 + 1 < n:
        mat[x2 + 1][y2 + 1] += 1

For each query defining a rectangle from (x1, y1) to (x2, y2):

  • Add 1 at the top-left corner (x1, y1) to start the effect
  • Subtract 1 at (x2+1, y1) if it's within bounds - this cancels the effect below row x2
  • Subtract 1 at (x1, y2+1) if it's within bounds - this cancels the effect right of column y2
  • Add 1 at (x2+1, y2+1) if it's within bounds - this corrects the double subtraction from the previous two operations

The boundary checks ensure we don't access indices outside the matrix.

Step 3: Reconstruct Using 2D Prefix Sums

for i in range(n):
    for j in range(n):
        if i:
            mat[i][j] += mat[i - 1][j]
        if j:
            mat[i][j] += mat[i][j - 1]
        if i and j:
            mat[i][j] -= mat[i - 1][j - 1]

This phase propagates the boundary markers throughout the matrix:

  • mat[i][j] += mat[i-1][j]: Add the accumulated value from the cell above
  • mat[i][j] += mat[i][j-1]: Add the accumulated value from the cell to the left
  • mat[i][j] -= mat[i-1][j-1]: Subtract the diagonal cell to avoid double-counting (since it was included in both the above and left cells)

The conditions if i: and if j: check that we're not at the first row or column, preventing out-of-bounds access.

Time Complexity: O(n² + q) where q is the number of queries

  • Marking boundaries: O(4q) = O(q)
  • Reconstruction: O(n²)

Space Complexity: O(n²) for the matrix itself

This approach is significantly more efficient than the naive O(n² × q) solution when dealing with multiple queries or large rectangular regions.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with a 4×4 matrix and 2 queries:

  • Query 1: [1, 1, 2, 2] - adds 1 to rectangle from (1,1) to (2,2)
  • Query 2: [0, 0, 1, 1] - adds 1 to rectangle from (0,0) to (1,1)

Initial State:

0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0

Step 1: Process Query 1 [1, 1, 2, 2]

Mark boundaries:

  • Add 1 at (1,1): the top-left corner
  • Subtract 1 at (3,1): stops effect after row 2
  • Subtract 1 at (1,3): stops effect after column 2
  • Add 1 at (3,3): corrects double subtraction
0  0  0  0
0  1  0 -1
0  0  0  0
0 -1  0  1

Step 2: Process Query 2 [0, 0, 1, 1]

Mark boundaries on top of existing marks:

  • Add 1 at (0,0): the top-left corner
  • Subtract 1 at (2,0): stops effect after row 1
  • Subtract 1 at (0,2): stops effect after column 1
  • Add 1 at (2,2): corrects double subtraction
1  0 -1  0
0  1  0 -1
-1 0  1  0
0 -1  0  1

Step 3: Reconstruct with 2D Prefix Sums

Process row by row, left to right:

Row 0:

  • (0,0): 1 (no cells above or left)
  • (0,1): 1 + 0 = 1 (add left cell)
  • (0,2): 1 + (-1) = 0 (add left cell)
  • (0,3): 0 + 0 = 0 (add left cell)

After row 0: [1, 1, 0, 0]

Row 1:

  • (1,0): 1 + 0 = 1 (add above cell)
  • (1,1): 1 + 1 + 1 - 1 = 2 (above + left - diagonal)
  • (1,2): 2 + 0 + 0 - 1 = 1 (above + left - diagonal)
  • (1,3): 1 + (-1) + 0 - 0 = 0 (above + left - diagonal)

After row 1: [1, 2, 1, 0]

Row 2:

  • (2,0): 1 + (-1) = 0
  • (2,1): 2 + 0 + 0 - 1 = 1
  • (2,2): 1 + 1 + 1 - 2 = 1
  • (2,3): 0 + 0 + 1 - 1 = 0

After row 2: [0, 1, 1, 0]

Row 3:

  • (3,0): 0 + 0 = 0
  • (3,1): 1 + (-1) + 0 - 0 = 0
  • (3,2): 1 + 0 + 0 - 1 = 0
  • (3,3): 0 + 1 + 0 - 0 = 1

Final Result:

1 1 0 0
1 2 1 0
0 1 1 0
0 0 0 0

We can verify this is correct:

  • Query 1 added 1 to cells (1,1), (1,2), (2,1), (2,2)
  • Query 2 added 1 to cells (0,0), (0,1), (1,0), (1,1)
  • Cell (1,1) was affected by both queries, so it has value 2
  • All other affected cells have value 1

Solution Implementation

1class Solution:
2    def rangeAddQueries(self, n: int, queries: List[List[int]]) -> List[List[int]]:
3        # Initialize n x n matrix with all zeros
4        matrix = [[0] * n for _ in range(n)]
5      
6        # Build difference array by marking boundaries of each query rectangle
7        for row1, col1, row2, col2 in queries:
8            # Mark the top-left corner with +1
9            matrix[row1][col1] += 1
10          
11            # Mark the row boundary (one row after bottom-right)
12            if row2 + 1 < n:
13                matrix[row2 + 1][col1] -= 1
14          
15            # Mark the column boundary (one column after bottom-right)
16            if col2 + 1 < n:
17                matrix[row1][col2 + 1] -= 1
18          
19            # Mark the diagonal boundary (restore the value for area outside rectangle)
20            if row2 + 1 < n and col2 + 1 < n:
21                matrix[row2 + 1][col2 + 1] += 1
22      
23        # Apply 2D prefix sum to get the final values
24        for row in range(n):
25            for col in range(n):
26                # Add value from the cell above
27                if row > 0:
28                    matrix[row][col] += matrix[row - 1][col]
29              
30                # Add value from the cell to the left
31                if col > 0:
32                    matrix[row][col] += matrix[row][col - 1]
33              
34                # Subtract the diagonal overlap (included twice above)
35                if row > 0 and col > 0:
36                    matrix[row][col] -= matrix[row - 1][col - 1]
37      
38        return matrix
39
1class Solution {
2    public int[][] rangeAddQueries(int n, int[][] queries) {
3        // Initialize n x n matrix with all zeros
4        int[][] matrix = new int[n][n];
5      
6        // Apply difference array technique for 2D range updates
7        // Mark the boundaries of each query rectangle
8        for (int[] query : queries) {
9            int row1 = query[0];
10            int col1 = query[1];
11            int row2 = query[2];
12            int col2 = query[3];
13          
14            // Mark the top-left corner with +1
15            matrix[row1][col1]++;
16          
17            // Mark the position below the bottom-right corner with -1
18            if (row2 + 1 < n) {
19                matrix[row2 + 1][col1]--;
20            }
21          
22            // Mark the position to the right of the bottom-right corner with -1
23            if (col2 + 1 < n) {
24                matrix[row1][col2 + 1]--;
25            }
26          
27            // Mark the diagonal position (row2+1, col2+1) with +1 to compensate
28            if (row2 + 1 < n && col2 + 1 < n) {
29                matrix[row2 + 1][col2 + 1]++;
30            }
31        }
32      
33        // Build the final matrix using 2D prefix sum
34        for (int row = 0; row < n; row++) {
35            for (int col = 0; col < n; col++) {
36                // Add value from the cell above
37                if (row > 0) {
38                    matrix[row][col] += matrix[row - 1][col];
39                }
40              
41                // Add value from the cell to the left
42                if (col > 0) {
43                    matrix[row][col] += matrix[row][col - 1];
44                }
45              
46                // Subtract the diagonal cell (included twice) to avoid double counting
47                if (row > 0 && col > 0) {
48                    matrix[row][col] -= matrix[row - 1][col - 1];
49                }
50            }
51        }
52      
53        return matrix;
54    }
55}
56
1class Solution {
2public:
3    vector<vector<int>> rangeAddQueries(int n, vector<vector<int>>& queries) {
4        // Initialize n x n matrix with all zeros
5        vector<vector<int>> matrix(n, vector<int>(n, 0));
6      
7        // Apply difference array technique in 2D
8        // Mark the boundaries of each query rectangle
9        for (const auto& query : queries) {
10            int row1 = query[0];
11            int col1 = query[1];
12            int row2 = query[2];
13            int col2 = query[3];
14          
15            // Mark the top-left corner with +1
16            matrix[row1][col1]++;
17          
18            // Mark the row after bottom boundary with -1
19            if (row2 + 1 < n) {
20                matrix[row2 + 1][col1]--;
21            }
22          
23            // Mark the column after right boundary with -1
24            if (col2 + 1 < n) {
25                matrix[row1][col2 + 1]--;
26            }
27          
28            // Mark the diagonal corner (row2+1, col2+1) with +1
29            // This compensates for the double subtraction
30            if (row2 + 1 < n && col2 + 1 < n) {
31                matrix[row2 + 1][col2 + 1]++;
32            }
33        }
34      
35        // Compute 2D prefix sum to get the final result
36        for (int i = 0; i < n; ++i) {
37            for (int j = 0; j < n; ++j) {
38                // Add value from the cell above
39                if (i > 0) {
40                    matrix[i][j] += matrix[i - 1][j];
41                }
42              
43                // Add value from the cell to the left
44                if (j > 0) {
45                    matrix[i][j] += matrix[i][j - 1];
46                }
47              
48                // Subtract the diagonal cell to avoid double counting
49                if (i > 0 && j > 0) {
50                    matrix[i][j] -= matrix[i - 1][j - 1];
51                }
52            }
53        }
54      
55        return matrix;
56    }
57};
58
1function rangeAddQueries(n: number, queries: number[][]): number[][] {
2    // Initialize n x n matrix with all zeros
3    const matrix: number[][] = Array(n).fill(0).map(() => Array(n).fill(0));
4  
5    // Apply difference array technique in 2D
6    // Mark the boundaries of each query rectangle
7    for (const query of queries) {
8        const row1 = query[0];
9        const col1 = query[1];
10        const row2 = query[2];
11        const col2 = query[3];
12      
13        // Mark the top-left corner with +1
14        matrix[row1][col1]++;
15      
16        // Mark the row after bottom boundary with -1
17        if (row2 + 1 < n) {
18            matrix[row2 + 1][col1]--;
19        }
20      
21        // Mark the column after right boundary with -1
22        if (col2 + 1 < n) {
23            matrix[row1][col2 + 1]--;
24        }
25      
26        // Mark the diagonal corner (row2+1, col2+1) with +1
27        // This compensates for the double subtraction
28        if (row2 + 1 < n && col2 + 1 < n) {
29            matrix[row2 + 1][col2 + 1]++;
30        }
31    }
32  
33    // Compute 2D prefix sum to get the final result
34    for (let i = 0; i < n; i++) {
35        for (let j = 0; j < n; j++) {
36            // Add value from the cell above
37            if (i > 0) {
38                matrix[i][j] += matrix[i - 1][j];
39            }
40          
41            // Add value from the cell to the left
42            if (j > 0) {
43                matrix[i][j] += matrix[i][j - 1];
44            }
45          
46            // Subtract the diagonal cell to avoid double counting
47            if (i > 0 && j > 0) {
48                matrix[i][j] -= matrix[i - 1][j - 1];
49            }
50        }
51    }
52  
53    return matrix;
54}
55

Time and Space Complexity

Time Complexity: O(q + n²) where q is the number of queries and n is the size of the matrix.

  • The first loop iterates through all queries and performs constant time operations for each query: O(q)
  • The second nested loop iterates through all n × n cells of the matrix to compute the prefix sum. Each cell performs constant time operations (at most 3 additions and 1 subtraction): O(n²)
  • Total time complexity: O(q + n²)

Space Complexity: O(n²)

  • The algorithm creates a 2D matrix mat of size n × n to store the difference array and final result: O(n²)
  • No additional auxiliary space is used beyond the output matrix
  • The space used for the input queries is not counted as it's part of the input
  • Total space complexity: O(n²)

The algorithm uses a 2D difference array technique combined with 2D prefix sum computation to efficiently handle range updates. Instead of updating all cells in each query rectangle (which would be O(q × n²) in the worst case), it marks only the corners of each rectangle in O(1) time per query, then reconstructs the final values using prefix sums in a single pass.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Boundary Marking Coordinates

One of the most common mistakes is incorrectly placing the boundary markers, especially confusing which cells need +1 or -1. Many developers mistakenly place markers at:

  • (x2, y1) instead of (x2+1, y1)
  • (x1, y2) instead of (x1, y2+1)
  • (x2, y2) instead of (x2+1, y2+1)

Why this happens: The intuition is that we want to mark the rectangle's boundaries, but the difference array technique requires marking positions outside the rectangle to stop the effect.

Solution: Remember that difference arrays mark where changes start and stop. The -1 markers must be placed one position beyond where you want the effect to end.

2. Missing Boundary Checks

Another frequent error is forgetting to check if x2+1 or y2+1 are within the matrix bounds before accessing those positions:

# WRONG - may cause IndexError
mat[x2 + 1][y1] -= 1  # What if x2 + 1 >= n?

# CORRECT
if x2 + 1 < n:
    mat[x2 + 1][y1] -= 1

Solution: Always verify that indices are within bounds before accessing array elements. When the boundary marker would be outside the matrix, it's safe to skip it since there's nothing beyond the matrix edge to affect.

3. Incorrect Order of Prefix Sum Operations

During reconstruction, applying the prefix sum operations in the wrong order or using wrong indices can lead to incorrect results:

# WRONG - using wrong indices
mat[i][j] += mat[i][j - 1]  # Should check j > 0 first
mat[i][j] += mat[i - 1][j]  # Should check i > 0 first

# WRONG - forgetting the subtraction
mat[i][j] += mat[i - 1][j]
mat[i][j] += mat[i][j - 1]
# Missing: mat[i][j] -= mat[i - 1][j - 1]

Solution: Follow the exact 2D prefix sum formula:

  • Add from above (if not first row)
  • Add from left (if not first column)
  • Subtract diagonal overlap (if not first row AND not first column)

4. Attempting In-Place Updates During Iteration

Some might try to optimize by processing queries and computing prefix sums simultaneously, which leads to incorrect results:

# WRONG - mixing boundary marking with prefix sum computation
for query in queries:
    # Mark boundaries
    # Then immediately try to compute prefix sums
    # This doesn't work because all boundaries must be marked first

Solution: Keep the two phases separate:

  1. First, mark ALL boundaries for ALL queries
  2. Then, perform the 2D prefix sum reconstruction in a single pass

5. Off-by-One Errors in Loop Conditions

Using incorrect loop conditions during prefix sum reconstruction:

# WRONG - using >= instead of >
if i >= 1:  # This works but i > 0 is clearer
    mat[i][j] += mat[i - 1][j]

# WRONG - starting from 1 but not adjusting logic
for i in range(1, n):  # Starting from 1
    mat[i][j] += mat[i - 1][j]  # OK
    if i:  # This is always True since i starts from 1
        # Redundant check

Solution: Use clear, consistent conditions. Starting loops from 0 and using if i > 0: or if i: makes the logic more straightforward and prevents confusion.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More