Facebook Pixel

1074. Number of Submatrices That Sum to Target

HardArrayHash TableMatrixPrefix Sum
Leetcode Link

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:

  1. Dimension reduction: The 2D problem is reduced to a 1D problem by fixing row boundaries and computing column sums
  2. Subarray sum counting: For each fixed pair of rows (i, j), it creates an array col where col[k] represents the sum of elements in column k from row i to row j
  3. 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.

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

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 index c1 to c2
  • 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 with d[0] = 1 (representing empty prefix)
  • Maintain a running sum s and counter cnt
  • For each element x in nums:
    • Add x to running sum: s += x
    • Check if s - target exists in the hash map. If it does, it means there are d[s - target] subarrays ending at current position with sum equal to target
    • Add this count to cnt: cnt += d[s - target]
    • Update hash map: d[s] += 1 to record this prefix sum

Main Algorithm:

  1. Get matrix dimensions: m rows and n columns
  2. Initialize answer counter ans = 0
  3. Iterate through all possible top rows i from 0 to m-1:
    • Create a column sum array col of size n, initialized to zeros
    • For each possible bottom row j from i to m-1:
      • Update column sums: For each column k, add matrix[j][k] to col[k]
        • At this point, col[k] contains the sum of elements in column k from row i to row j
      • Solve 1D problem: Call f(col) to count how many subarrays in col sum to target
        • Each valid subarray represents a submatrix with corners (i, left) and (j, right)
      • Add the count to ans: ans += f(col)
  4. Return the total count ans

Why this works:

  • By fixing rows i and j, we reduce the problem to finding contiguous column ranges
  • The column sum array col represents the "compressed" view of the submatrix between rows i and j
  • 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 row i and bottom row j

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 Evaluator

Example 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:

  1. The entire first row: [[1, 2, 1]]
  2. The first column from rows 0-2: [[1], [1], [2]]
  3. 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 to m-1, iterating m times
  • The middle loop runs from j = i to m-1, which in worst case iterates O(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 size n once: O(n)
  • 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 size n: O(n)
  • Inside function f, a dictionary d that stores at most n+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).

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

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

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

Load More