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:
-
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
- Add 1 at the top-left corner
-
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
- For each cell, accumulate values from the cell above (
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.
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 rowx2
- Subtract 1 at
(x1, y2+1)
if it's within bounds - this cancels the effect right of columny2
- 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 abovemat[i][j] += mat[i][j-1]
: Add the accumulated value from the cell to the leftmat[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 EvaluatorExample 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 sizen × 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:
- First, mark ALL boundaries for ALL queries
- 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.
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
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!