1074. Number of Submatrices That Sum to Target
Problem Description
You are given a 2D matrix of integers and a target value. Your task is to count how many submatrices within this matrix have elements that sum up to the target value.
A submatrix is defined by its top-left corner (x1, y1)
and bottom-right corner (x2, y2)
. It includes all cells matrix[x][y]
where x1 ≤ x ≤ x2
and y1 ≤ y ≤ y2
.
Two submatrices are considered different if they have at least one different coordinate. For example, if the top-left corners are different, they are counted as separate submatrices even if they might contain the same values.
The solution uses a technique that combines:
- Dimension reduction: The 2D problem is reduced to a 1D problem by fixing row boundaries and computing column sums
- Subarray sum counting: For each fixed pair of rows
(i, j)
, it creates an arraycol
wherecol[k]
represents the sum of elements in columnk
from rowi
to rowj
- Hash map optimization: The helper function
f
counts subarrays with sum equal to target using the classic prefix sum + hash map approach
The algorithm iterates through all possible top rows i
, then for each top row, it considers all possible bottom rows j ≥ i
. For each such pair, it maintains running column sums and uses the 1D subarray sum problem solution to count valid submatrices.
The time complexity is O(m²n)
where m
is the number of rows and n
is the number of columns, as we iterate through all row pairs and process each column.
Intuition
Finding all submatrices with a target sum in a 2D matrix seems overwhelming at first - we'd need to check every possible rectangle, which could be O(m²n²)
rectangles, and calculating each sum could take O(mn)
time, leading to a very inefficient solution.
The key insight is recognizing that this 2D problem can be transformed into multiple 1D problems. We know how to efficiently solve "count subarrays with target sum" in 1D using prefix sums and a hash map in O(n)
time. Can we leverage this?
Think about fixing the top and bottom boundaries of our submatrix. If we fix rows i
(top) and j
(bottom), then any submatrix between these rows is determined solely by which columns we choose. More importantly, if we compress all values between rows i
and j
into a single array by summing each column, finding submatrices becomes equivalent to finding subarrays in this compressed array.
For example, if we have a matrix and fix rows 1 to 3, we can create an array where each element is the sum of that column from row 1 to row 3. Now finding a submatrix with target sum between columns c1
and c2
is the same as finding a subarray with target sum in our compressed array.
This transformation works because:
- The sum of a submatrix from
(i, c1)
to(j, c2)
equals the sum of compressed array elements from indexc1
toc2
- We can incrementally build these compressed arrays as we expand our bottom boundary, avoiding redundant calculations
By iterating through all possible pairs of top and bottom rows (i, j)
where i ≤ j
, we ensure we consider every possible submatrix. For each pair, we solve the 1D subarray problem, accumulating the total count. This reduces our time complexity from potentially O(m²n² × mn)
to O(m²n)
, making the problem tractable.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation consists of two main components: a helper function for the 1D subarray problem and the main logic that reduces the 2D problem to multiple 1D problems.
Helper Function f(nums)
:
This function counts subarrays with sum equal to target
using the prefix sum technique:
- Initialize a hash map
d
withd[0] = 1
(representing empty prefix) - Maintain a running sum
s
and countercnt
- For each element
x
innums
:- Add
x
to running sum:s += x
- Check if
s - target
exists in the hash map. If it does, it means there ared[s - target]
subarrays ending at current position with sum equal totarget
- Add this count to
cnt
:cnt += d[s - target]
- Update hash map:
d[s] += 1
to record this prefix sum
- Add
Main Algorithm:
- Get matrix dimensions:
m
rows andn
columns - Initialize answer counter
ans = 0
- Iterate through all possible top rows
i
from0
tom-1
:- Create a column sum array
col
of sizen
, initialized to zeros - For each possible bottom row
j
fromi
tom-1
:- Update column sums: For each column
k
, addmatrix[j][k]
tocol[k]
- At this point,
col[k]
contains the sum of elements in columnk
from rowi
to rowj
- At this point,
- Solve 1D problem: Call
f(col)
to count how many subarrays incol
sum totarget
- Each valid subarray represents a submatrix with corners
(i, left)
and(j, right)
- Each valid subarray represents a submatrix with corners
- Add the count to
ans
:ans += f(col)
- Update column sums: For each column
- Create a column sum array
- Return the total count
ans
Why this works:
- By fixing rows
i
andj
, we reduce the problem to finding contiguous column ranges - The column sum array
col
represents the "compressed" view of the submatrix between rowsi
andj
- The incremental update of
col
(adding one row at a time) ensures we don't redundantly recalculate sums - Each call to
f(col)
efficiently counts all valid submatrices with top rowi
and bottom rowj
Time Complexity: O(m²n)
- we have O(m²)
row pairs, and for each pair, we process n
columns
Space Complexity: O(n)
- for the column sum array and hash map in function f
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 small example to illustrate the solution approach.
Consider this 3×3 matrix with target = 4:
matrix = [[1, 2, 1], [1, 1, 1], [2, 0, 1]] target = 4
Step 1: Fix top row i = 0
When j = 0 (considering only row 0):
- col = [1, 2, 1] (just row 0)
- Find subarrays in [1, 2, 1] that sum to 4:
- Running sums: 1, 3, 4
- Subarray [1, 2, 1] sums to 4 ✓
- Count = 1
When j = 1 (considering rows 0-1):
- col = [1+1, 2+1, 1+1] = [2, 3, 2] (sum of rows 0-1)
- Find subarrays in [2, 3, 2] that sum to 4:
- No subarrays sum to 4
- Count = 0
When j = 2 (considering rows 0-2):
- col = [2+2, 3+0, 2+1] = [4, 3, 3] (sum of rows 0-2)
- Find subarrays in [4, 3, 3] that sum to 4:
- Subarray [4] sums to 4 ✓
- Count = 1
Step 2: Fix top row i = 1
When j = 1 (considering only row 1):
- col = [1, 1, 1] (just row 1)
- Find subarrays in [1, 1, 1] that sum to 4:
- No subarrays sum to 4
- Count = 0
When j = 2 (considering rows 1-2):
- col = [1+2, 1+0, 1+1] = [3, 1, 2] (sum of rows 1-2)
- Find subarrays in [3, 1, 2] that sum to 4:
- Running sums: 3, 4, 6
- Subarray [3, 1] sums to 4 ✓
- Count = 1
Step 3: Fix top row i = 2
When j = 2 (considering only row 2):
- col = [2, 0, 1] (just row 2)
- Find subarrays in [2, 0, 1] that sum to 4:
- No subarrays sum to 4
- Count = 0
Total count = 1 + 0 + 1 + 0 + 1 + 0 = 3
The three submatrices that sum to 4 are:
- The entire first row: [[1, 2, 1]]
- The first column from rows 0-2: [[1], [1], [2]]
- The first two columns from rows 1-2: [[1, 1], [2, 0]]
This demonstrates how the algorithm systematically examines all possible row boundaries and reduces each configuration to a 1D subarray sum problem.
Solution Implementation
1class Solution:
2 def numSubmatrixSumTarget(self, matrix: List[List[int]], target: int) -> int:
3 def count_subarrays_with_target_sum(nums: List[int]) -> int:
4 """
5 Count the number of subarrays in nums that sum to target.
6 Uses prefix sum with hashmap to achieve O(n) time complexity.
7 """
8 # Store frequency of each prefix sum
9 prefix_sum_count = defaultdict(int)
10 # Empty subarray has sum 0
11 prefix_sum_count[0] = 1
12
13 count = 0
14 current_sum = 0
15
16 for num in nums:
17 current_sum += num
18 # If (current_sum - target) exists in map, we found subarrays ending here
19 # that sum to target
20 count += prefix_sum_count[current_sum - target]
21 # Add current prefix sum to map
22 prefix_sum_count[current_sum] += 1
23
24 return count
25
26 rows, cols = len(matrix), len(matrix[0])
27 result = 0
28
29 # Fix top row of submatrix
30 for top_row in range(rows):
31 # Initialize column sums for current top row
32 column_sums = [0] * cols
33
34 # Extend bottom row of submatrix
35 for bottom_row in range(top_row, rows):
36 # Update column sums to include current bottom row
37 for col in range(cols):
38 column_sums[col] += matrix[bottom_row][col]
39
40 # Now column_sums[k] = sum of elements in column k from top_row to bottom_row
41 # Find number of column ranges that sum to target
42 result += count_subarrays_with_target_sum(column_sums)
43
44 return result
45
1class Solution {
2 /**
3 * Count the number of submatrices whose sum equals the target.
4 * Uses a dimension reduction technique by fixing top and bottom rows,
5 * then converting the problem to a 1D subarray sum problem.
6 *
7 * @param matrix The input 2D matrix
8 * @param target The target sum to find
9 * @return The count of submatrices with sum equal to target
10 */
11 public int numSubmatrixSumTarget(int[][] matrix, int target) {
12 int rows = matrix.length;
13 int cols = matrix[0].length;
14 int result = 0;
15
16 // Fix the top row of the submatrix
17 for (int topRow = 0; topRow < rows; topRow++) {
18 // Array to store column sums between topRow and bottomRow
19 int[] columnSums = new int[cols];
20
21 // Extend the bottom row of the submatrix
22 for (int bottomRow = topRow; bottomRow < rows; bottomRow++) {
23 // Update column sums by adding current row values
24 for (int col = 0; col < cols; col++) {
25 columnSums[col] += matrix[bottomRow][col];
26 }
27
28 // Find number of subarrays in columnSums that sum to target
29 result += f(columnSums, target);
30 }
31 }
32
33 return result;
34 }
35
36 /**
37 * Count the number of subarrays in the given array that sum to target.
38 * Uses prefix sum with hashmap to achieve O(n) time complexity.
39 *
40 * @param nums The input array
41 * @param target The target sum to find
42 * @return The count of subarrays with sum equal to target
43 */
44 private int f(int[] nums, int target) {
45 // Map to store prefix sum frequencies
46 // Key: prefix sum value, Value: frequency of that sum
47 Map<Integer, Integer> prefixSumFreq = new HashMap<>();
48
49 // Empty subarray has sum 0
50 prefixSumFreq.put(0, 1);
51
52 int currentSum = 0;
53 int count = 0;
54
55 // Iterate through the array
56 for (int num : nums) {
57 // Update current prefix sum
58 currentSum += num;
59
60 // Check if there exists a prefix sum such that
61 // currentSum - prefixSum = target (i.e., prefixSum = currentSum - target)
62 count += prefixSumFreq.getOrDefault(currentSum - target, 0);
63
64 // Add current prefix sum to the map or increment its frequency
65 prefixSumFreq.merge(currentSum, 1, Integer::sum);
66 }
67
68 return count;
69 }
70}
71
1class Solution {
2public:
3 int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
4 int rows = matrix.size();
5 int cols = matrix[0].size();
6 int result = 0;
7
8 // Fix the top row of the submatrix
9 for (int topRow = 0; topRow < rows; ++topRow) {
10 // Column sum array to store cumulative sums from topRow to bottomRow
11 vector<int> columnSums(cols, 0);
12
13 // Extend the bottom row of the submatrix
14 for (int bottomRow = topRow; bottomRow < rows; ++bottomRow) {
15 // Update column sums for the current bottom row
16 for (int col = 0; col < cols; ++col) {
17 columnSums[col] += matrix[bottomRow][col];
18 }
19
20 // Count subarrays in columnSums that sum to target
21 // This reduces the 2D problem to a 1D subarray sum problem
22 result += countSubarraySum(columnSums, target);
23 }
24 }
25
26 return result;
27 }
28
29private:
30 int countSubarraySum(vector<int>& nums, int target) {
31 // Map to store prefix sum frequencies
32 // Initialize with {0: 1} to handle subarrays starting from index 0
33 unordered_map<int, int> prefixSumFreq{{0, 1}};
34 int count = 0;
35 int currentSum = 0;
36
37 // Iterate through the array
38 for (int& num : nums) {
39 currentSum += num;
40
41 // Check if there exists a prefix sum such that
42 // currentSum - prefixSum = target
43 // i.e., prefixSum = currentSum - target
44 if (prefixSumFreq.count(currentSum - target)) {
45 count += prefixSumFreq[currentSum - target];
46 }
47
48 // Add current prefix sum to the map
49 ++prefixSumFreq[currentSum];
50 }
51
52 return count;
53 }
54};
55
1/**
2 * Counts the number of submatrices that sum to target
3 * @param matrix - The input 2D matrix
4 * @param target - The target sum to find
5 * @returns The count of submatrices with sum equal to target
6 */
7function numSubmatrixSumTarget(matrix: number[][], target: number): number {
8 const rowCount: number = matrix.length;
9 const colCount: number = matrix[0].length;
10 let totalCount: number = 0;
11
12 // Fix the top row of the submatrix
13 for (let topRow = 0; topRow < rowCount; topRow++) {
14 // Initialize column sums array for current top row
15 const columnSums: number[] = new Array(colCount).fill(0);
16
17 // Extend the bottom row of the submatrix
18 for (let bottomRow = topRow; bottomRow < rowCount; bottomRow++) {
19 // Add current row values to column sums
20 for (let col = 0; col < colCount; col++) {
21 columnSums[col] += matrix[bottomRow][col];
22 }
23
24 // Count subarrays in the compressed 1D array that sum to target
25 totalCount += countSubarrayWithSum(columnSums, target);
26 }
27 }
28
29 return totalCount;
30}
31
32/**
33 * Counts the number of subarrays in a 1D array that sum to target
34 * Uses prefix sum with hashmap approach
35 * @param nums - The input 1D array
36 * @param target - The target sum to find
37 * @returns The count of subarrays with sum equal to target
38 */
39function countSubarrayWithSum(nums: number[], target: number): number {
40 // Map to store prefix sum frequencies
41 const prefixSumFrequency: Map<number, number> = new Map();
42 // Initialize with 0 sum having frequency 1 (empty subarray)
43 prefixSumFrequency.set(0, 1);
44
45 let subarrayCount: number = 0;
46 let currentPrefixSum: number = 0;
47
48 // Iterate through each element in the array
49 for (const num of nums) {
50 // Update current prefix sum
51 currentPrefixSum += num;
52
53 // Check if there exists a prefix sum such that
54 // currentPrefixSum - prefixSum = target
55 const requiredPrefixSum: number = currentPrefixSum - target;
56 if (prefixSumFrequency.has(requiredPrefixSum)) {
57 subarrayCount += prefixSumFrequency.get(requiredPrefixSum)!;
58 }
59
60 // Update frequency of current prefix sum
61 const currentFrequency: number = prefixSumFrequency.get(currentPrefixSum) || 0;
62 prefixSumFrequency.set(currentPrefixSum, currentFrequency + 1);
63 }
64
65 return subarrayCount;
66}
67
Time and Space Complexity
Time Complexity: O(m² × n)
The algorithm uses a technique that combines 2D prefix sum with the subarray sum problem:
- The outer loop runs from
i = 0
tom-1
, iteratingm
times - The middle loop runs from
j = i
tom-1
, which in worst case iteratesO(m)
times - Together, these two nested loops create
O(m²)
iterations - For each pair
(i, j)
, we:- Update the column sums by iterating through
n
columns:O(n)
- Call function
f(col)
which iterates through the array of sizen
once:O(n)
- Update the column sums by iterating through
- Total operations per
(i, j)
pair:O(n)
- Overall time complexity:
O(m²) × O(n) = O(m² × n)
Space Complexity: O(n)
The space usage consists of:
col
array of sizen
:O(n)
- Inside function
f
, a dictionaryd
that stores at mostn+1
unique prefix sums (one for each position plus the initial 0):O(n)
- Other variables (
cnt
,s
,ans
, loop variables):O(1)
- The maximum space used at any point is
O(n)
Note that we don't count the input matrix in the space complexity as it's given as input. The algorithm efficiently reuses the col
array across iterations rather than storing all possible column sums simultaneously.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Initialization of Prefix Sum HashMap
A critical pitfall is forgetting to initialize the prefix sum hashmap with prefix_sum_count[0] = 1
. This initialization represents the empty prefix (no elements selected yet) and is essential for correctly counting subarrays that start from the beginning.
Wrong Implementation:
def count_subarrays_with_target_sum(nums):
prefix_sum_count = defaultdict(int)
# Missing: prefix_sum_count[0] = 1
count = 0
current_sum = 0
for num in nums:
current_sum += num
count += prefix_sum_count[current_sum - target]
prefix_sum_count[current_sum] += 1
return count
Why it fails: Without prefix_sum_count[0] = 1
, subarrays starting from index 0 won't be counted. For example, if nums = [5]
and target = 5
, the count would incorrectly be 0 instead of 1.
2. Column Sum Array Not Reset Between Different Top Rows
Another common mistake is reusing the same column sum array across different top rows without resetting it, leading to incorrect accumulated sums.
Wrong Implementation:
rows, cols = len(matrix), len(matrix[0])
result = 0
column_sums = [0] * cols # Created once outside the loop
for top_row in range(rows):
# column_sums not reset - carries over values from previous iterations
for bottom_row in range(top_row, rows):
for col in range(cols):
column_sums[col] += matrix[bottom_row][col]
result += count_subarrays_with_target_sum(column_sums)
Correct Implementation:
for top_row in range(rows):
column_sums = [0] * cols # Reset for each new top row
for bottom_row in range(top_row, rows):
# ... rest of the code
3. Order of Operations in Prefix Sum Calculation
Updating the hashmap before checking for complementary sums can lead to double-counting when the target is 0 or when specific patterns exist.
Potentially Problematic:
for num in nums: current_sum += num prefix_sum_count[current_sum] += 1 # Update first count += prefix_sum_count[current_sum - target] # Then check
Correct Order:
for num in nums: current_sum += num count += prefix_sum_count[current_sum - target] # Check first prefix_sum_count[current_sum] += 1 # Then update
The correct order ensures we're looking for previously seen prefix sums, not including the current position itself in the count.
4. Integer Overflow in Large Matrices
While Python handles arbitrary precision integers automatically, in other languages or when optimizing with NumPy, large matrices with large values could cause integer overflow when computing sums.
Solution: Be aware of the value ranges in your matrix and use appropriate data types (e.g., long long
in C++ or explicit dtype specification in NumPy).
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
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!