Facebook Pixel

1504. Count Submatrices With All Ones

Problem Description

You are given a binary matrix mat of size m x n containing only 0s and 1s. Your task is to count the total number of submatrices that contain only 1s.

A submatrix is any contiguous rectangular portion of the original matrix. It can be defined by selecting a top-left corner (r1, c1) and a bottom-right corner (r2, c2) where r1 ≤ r2 and c1 ≤ c2. The submatrix includes all elements mat[i][j] where r1 ≤ i ≤ r2 and c1 ≤ j ≤ c2.

For example, if you have a 3x3 matrix:

1 1 1
1 1 1
1 1 1

The number of submatrices with all 1s includes:

  • 9 submatrices of size 1x1
  • 6 submatrices of size 1x2
  • 3 submatrices of size 1x3
  • 6 submatrices of size 2x1
  • 4 submatrices of size 2x2
  • 2 submatrices of size 2x3
  • 3 submatrices of size 3x1
  • 2 submatrices of size 3x2
  • 1 submatrix of size 3x3

The total would be 36 submatrices.

The solution uses a preprocessing step to calculate g[i][j], which represents the maximum width of consecutive 1s extending left from position (i, j). Then for each position (i, j), it considers all possible submatrices with (i, j) as the bottom-right corner by iterating upward through rows and tracking the minimum width available at each height.

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

Intuition

The key insight is to fix the bottom-right corner of potential submatrices and count how many valid submatrices end at each position.

Consider any position (i, j) in the matrix. If we want to count all submatrices that have (i, j) as their bottom-right corner, we need to consider all possible top-left corners that form valid all-1 submatrices with this point.

The challenge is efficiently counting these submatrices. A naive approach would check every possible submatrix, but this would be too slow. Instead, we can make a crucial observation: for any column j, as we move upward from row i, the maximum width of valid submatrices is constrained by the minimum width seen so far.

To understand this better, imagine we're at position (i, j) and looking upward. For row i, we might have 5 consecutive 1s extending to the left. For row i-1, we might have only 3 consecutive 1s. This means any submatrix that includes both rows can have a maximum width of 3, not 5.

This leads us to precompute g[i][j] - the number of consecutive 1s extending left from position (i, j) in each row. This preprocessing allows us to quickly determine the maximum width available at any position.

Then, for each position (i, j), we iterate upward from row i to row 0. As we include each new row k, we update our running minimum width (col = min(col, g[k][j])). This minimum represents the maximum width of submatrices that span from row k to row i and end at column j. We add this value to our answer because it represents the number of valid submatrices with heights from (k, j) to (i, j) and varying widths from 1 to col.

Learn more about Stack, Dynamic Programming and Monotonic Stack patterns.

Solution Approach

The solution consists of two main phases: preprocessing and counting.

Phase 1: Preprocessing - Building the Width Array g

First, we create a 2D array g with the same dimensions as the input matrix. For each position (i, j), g[i][j] stores the maximum width of consecutive 1s extending from position (i, j) to the left in row i.

for i in range(m):
    for j in range(n):
        if mat[i][j]:
            g[i][j] = 1 if j == 0 else 1 + g[i][j - 1]

For each cell:

  • If mat[i][j] = 0, then g[i][j] = 0 (remains initialized value)
  • If mat[i][j] = 1 and it's the first column (j = 0), then g[i][j] = 1
  • If mat[i][j] = 1 and it's not the first column, then g[i][j] = 1 + g[i][j-1], extending the consecutive 1s count from the left

Phase 2: Counting Submatrices

For each position (i, j) as a potential bottom-right corner:

for i in range(m):
    for j in range(n):
        col = inf
        for k in range(i, -1, -1):
            col = min(col, g[k][j])
            ans += col

We iterate upward from row i to row 0 (represented by variable k). As we include each row:

  1. Update col = min(col, g[k][j]) - this maintains the minimum width available from row k to row i
  2. Add col to the answer - this represents all submatrices with:
    • Bottom-right corner at (i, j)
    • Top row at k
    • Bottom row at i
    • Width ranging from 1 to col

The reason we add col directly is that if the minimum width is col, we can form col different submatrices with this height configuration - one for each possible width from 1 to col.

Time Complexity: O(m²n) where we have O(mn) for preprocessing and O(m²n) for the counting phase.

Space Complexity: O(mn) for the auxiliary array g.

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 the matrix:

mat = [
  [1, 1, 0],
  [1, 1, 1],
  [0, 1, 1]
]

Phase 1: Build the width array g

We calculate g[i][j] = number of consecutive 1s extending left from position (i,j):

Row 0:

  • g[0][0] = 1 (mat[0][0]=1, first column)
  • g[0][1] = 2 (mat[0][1]=1, extends from g[0][0])
  • g[0][2] = 0 (mat[0][2]=0)

Row 1:

  • g[1][0] = 1 (mat[1][0]=1, first column)
  • g[1][1] = 2 (mat[1][1]=1, extends from g[1][0])
  • g[1][2] = 3 (mat[1][2]=1, extends from g[1][1])

Row 2:

  • g[2][0] = 0 (mat[2][0]=0)
  • g[2][1] = 1 (mat[2][1]=1, but g[2][0]=0 so starts fresh)
  • g[2][2] = 2 (mat[2][2]=1, extends from g[2][1])

Result:

g = [
  [1, 2, 0],
  [1, 2, 3],
  [0, 1, 2]
]

Phase 2: Count submatrices

Now we fix each position as bottom-right corner and count valid submatrices:

Position (0,0): g[0][0] = 1

  • k=0: col = min(∞, 1) = 1, add 1
  • Total: 1

Position (0,1): g[0][1] = 2

  • k=0: col = min(∞, 2) = 2, add 2
  • Total: 2

Position (0,2): g[0][2] = 0

  • k=0: col = min(∞, 0) = 0, add 0
  • Total: 0

Position (1,0): g[1][0] = 1

  • k=1: col = min(∞, 1) = 1, add 1
  • k=0: col = min(1, 1) = 1, add 1
  • Total: 2

Position (1,1): g[1][1] = 2

  • k=1: col = min(∞, 2) = 2, add 2 (covers 2 submatrices of height 1)
  • k=0: col = min(2, 2) = 2, add 2 (covers 2 submatrices of height 2)
  • Total: 4

Position (1,2): g[1][2] = 3

  • k=1: col = min(∞, 3) = 3, add 3
  • k=0: col = min(3, 0) = 0, add 0
  • Total: 3

Position (2,0): g[2][0] = 0

  • k=2: col = min(∞, 0) = 0, add 0
  • k=1: col = min(0, 1) = 0, add 0
  • k=0: col = min(0, 1) = 0, add 0
  • Total: 0

Position (2,1): g[2][1] = 1

  • k=2: col = min(∞, 1) = 1, add 1
  • k=1: col = min(1, 2) = 1, add 1
  • k=0: col = min(1, 2) = 1, add 1
  • Total: 3

Position (2,2): g[2][2] = 2

  • k=2: col = min(∞, 2) = 2, add 2
  • k=1: col = min(2, 3) = 2, add 2
  • k=0: col = min(2, 0) = 0, add 0
  • Total: 4

Final Answer: 1 + 2 + 0 + 2 + 4 + 3 + 0 + 3 + 4 = 19

The 19 submatrices consist of various rectangles with all 1s, including single cells like [1,1], horizontal strips like the two cells at positions (0,0)-(0,1), vertical strips, and larger rectangles like the 2x2 square at positions (0,0)-(1,1).

Solution Implementation

1class Solution:
2    def numSubmat(self, mat: List[List[int]]) -> int:
3        # Get matrix dimensions
4        rows, cols = len(mat), len(mat[0])
5      
6        # Create a helper matrix to store consecutive 1s count ending at each position
7        # consecutive_ones[i][j] = number of consecutive 1s ending at position (i, j) in row i
8        consecutive_ones = [[0] * cols for _ in range(rows)]
9      
10        # Build the consecutive ones matrix
11        # For each position, count how many consecutive 1s there are to its left (including itself)
12        for i in range(rows):
13            for j in range(cols):
14                if mat[i][j] == 1:
15                    # If it's a 1, either start counting (j=0) or add to previous count
16                    consecutive_ones[i][j] = 1 if j == 0 else consecutive_ones[i][j - 1] + 1
17                # If it's a 0, the value remains 0 (already initialized)
18      
19        # Count all possible submatrices
20        total_submatrices = 0
21      
22        # For each position (i, j) as the bottom-right corner of potential submatrices
23        for i in range(rows):
24            for j in range(cols):
25                # Track the minimum width available as we go up rows
26                min_width = float('inf')
27              
28                # Try all possible top-left corners with same column j or less
29                # k represents the top row of the submatrix
30                for k in range(i, -1, -1):
31                    # Update minimum width (bottleneck for valid submatrix width)
32                    min_width = min(min_width, consecutive_ones[k][j])
33                  
34                    # Add count of all submatrices with bottom-right at (i,j) 
35                    # and top row at k, with width up to min_width
36                    total_submatrices += min_width
37      
38        return total_submatrices
39
1class Solution {
2    public int numSubmat(int[][] mat) {
3        int rows = mat.length;
4        int cols = mat[0].length;
5      
6        // consecutiveOnes[i][j] stores the number of consecutive 1s 
7        // ending at position (i, j) in row i
8        int[][] consecutiveOnes = new int[rows][cols];
9      
10        // Build the consecutive ones array for each row
11        for (int row = 0; row < rows; row++) {
12            for (int col = 0; col < cols; col++) {
13                if (mat[row][col] == 1) {
14                    // If current element is 1, calculate consecutive 1s ending here
15                    if (col == 0) {
16                        consecutiveOnes[row][col] = 1;
17                    } else {
18                        consecutiveOnes[row][col] = 1 + consecutiveOnes[row][col - 1];
19                    }
20                }
21                // If mat[row][col] is 0, consecutiveOnes[row][col] remains 0
22            }
23        }
24      
25        int totalSubmatrices = 0;
26      
27        // For each position (row, col) as the bottom-right corner of submatrices
28        for (int bottomRow = 0; bottomRow < rows; bottomRow++) {
29            for (int rightCol = 0; rightCol < cols; rightCol++) {
30                // Track the minimum width available for submatrices
31                int minWidth = Integer.MAX_VALUE;
32              
33                // Iterate from current row upwards to form submatrices
34                // with (bottomRow, rightCol) as bottom-right corner
35                for (int topRow = bottomRow; topRow >= 0 && minWidth > 0; topRow--) {
36                    // Update minimum width based on consecutive 1s at current row
37                    minWidth = Math.min(minWidth, consecutiveOnes[topRow][rightCol]);
38                  
39                    // Add the number of valid submatrices with height from topRow to bottomRow
40                    // and width up to minWidth
41                    totalSubmatrices += minWidth;
42                }
43            }
44        }
45      
46        return totalSubmatrices;
47    }
48}
49
1class Solution {
2public:
3    int numSubmat(vector<vector<int>>& mat) {
4        int rows = mat.size();
5        int cols = mat[0].size();
6      
7        // consecutiveOnes[i][j] stores the number of consecutive 1s 
8        // ending at position (i, j) in row i
9        vector<vector<int>> consecutiveOnes(rows, vector<int>(cols, 0));
10      
11        // Precompute consecutive 1s for each row
12        for (int row = 0; row < rows; ++row) {
13            for (int col = 0; col < cols; ++col) {
14                if (mat[row][col] == 1) {
15                    // If current cell is 1, count consecutive 1s from left
16                    if (col == 0) {
17                        consecutiveOnes[row][col] = 1;
18                    } else {
19                        consecutiveOnes[row][col] = 1 + consecutiveOnes[row][col - 1];
20                    }
21                }
22                // If current cell is 0, consecutiveOnes remains 0 (default value)
23            }
24        }
25      
26        int totalSubmatrices = 0;
27      
28        // For each position (row, col) as the bottom-right corner of submatrices
29        for (int row = 0; row < rows; ++row) {
30            for (int col = 0; col < cols; ++col) {
31                int minWidth = INT_MAX;  // Minimum width of valid submatrix
32              
33                // Iterate upward from current row to find all valid submatrices
34                // with bottom-right corner at (row, col)
35                for (int topRow = row; topRow >= 0 && minWidth > 0; --topRow) {
36                    // Update minimum width based on consecutive 1s at current level
37                    minWidth = min(minWidth, consecutiveOnes[topRow][col]);
38                  
39                    // Add the number of valid submatrices with height from topRow to row
40                    // and width up to minWidth
41                    totalSubmatrices += minWidth;
42                }
43            }
44        }
45      
46        return totalSubmatrices;
47    }
48};
49
1/**
2 * Counts the number of submatrices that contain only 1s
3 * @param mat - Binary matrix containing 0s and 1s
4 * @returns Total count of submatrices with all 1s
5 */
6function numSubmat(mat: number[][]): number {
7    const rows: number = mat.length;
8    const cols: number = mat[0].length;
9  
10    // Create a 2D array to store the count of consecutive 1s ending at each position
11    // consecutiveOnes[i][j] represents the number of consecutive 1s from left to position (i, j)
12    const consecutiveOnes: number[][] = Array.from(
13        { length: rows }, 
14        () => Array(cols).fill(0)
15    );
16
17    // Calculate consecutive 1s for each row
18    for (let row = 0; row < rows; row++) {
19        for (let col = 0; col < cols; col++) {
20            if (mat[row][col] === 1) {
21                // If current cell is 1, add 1 to the consecutive count from the left
22                consecutiveOnes[row][col] = col === 0 ? 1 : 1 + consecutiveOnes[row][col - 1];
23            }
24        }
25    }
26
27    let totalSubmatrices: number = 0;
28  
29    // For each position (row, col), calculate all possible submatrices ending at this position
30    for (let row = 0; row < rows; row++) {
31        for (let col = 0; col < cols; col++) {
32            let minWidth: number = Infinity;
33          
34            // Iterate from current row upwards to form submatrices with different heights
35            for (let topRow = row; topRow >= 0; topRow--) {
36                // The width of submatrix is limited by the minimum consecutive 1s in the column range
37                minWidth = Math.min(minWidth, consecutiveOnes[topRow][col]);
38                // Add the number of submatrices with height (row - topRow + 1) and various widths
39                totalSubmatrices += minWidth;
40            }
41        }
42    }
43
44    return totalSubmatrices;
45}
46

Time and Space Complexity

Time Complexity: O(m² × n)

The algorithm consists of two main parts:

  1. First nested loop (preprocessing): Iterates through all m × n elements to compute the consecutive 1s extending left from each position. This takes O(m × n) time.

  2. Second triple nested loop (counting submatrices):

    • The outer two loops iterate through all positions (i, j) in the matrix: O(m × n) iterations
    • For each position (i, j), the innermost loop iterates from row i up to row 0, which is at most m iterations
    • Inside the innermost loop, we perform constant time operations (min comparison and addition)
    • Total: O(m × n × m) = O(m² × n)

Since O(m × n) + O(m² × n) = O(m² × n), the overall time complexity is O(m² × n).

Space Complexity: O(m × n)

The algorithm uses:

  • A 2D array g of size m × n to store the lengths of consecutive 1s extending left
  • A few constant space variables (ans, col, loop counters)

Therefore, the space complexity is O(m × n) where m and n are the number of rows and columns of the matrix, respectively.

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

Common Pitfalls

1. Incorrect Width Calculation Direction

A common mistake is calculating the consecutive 1s count in the wrong direction. Some might try to count consecutive 1s extending to the right instead of to the left, which breaks the algorithm's logic when using each position as a bottom-right corner.

Incorrect approach:

# Wrong: counting to the right
for i in range(rows):
    for j in range(cols - 1, -1, -1):
        if mat[i][j] == 1:
            consecutive_ones[i][j] = 1 if j == cols - 1 else consecutive_ones[i][j + 1] + 1

Correct approach:

# Correct: counting to the left
for i in range(rows):
    for j in range(cols):
        if mat[i][j] == 1:
            consecutive_ones[i][j] = 1 if j == 0 else consecutive_ones[i][j - 1] + 1

2. Misunderstanding the Minimum Width Logic

Another pitfall is not properly maintaining the minimum width constraint when extending the submatrix upward. Each row added might have a different maximum width of consecutive 1s, and the valid submatrix width is constrained by the minimum across all included rows.

Incorrect approach:

for i in range(rows):
    for j in range(cols):
        for k in range(i, -1, -1):
            # Wrong: directly using the width at current row
            total_submatrices += consecutive_ones[k][j]

Correct approach:

for i in range(rows):
    for j in range(cols):
        min_width = float('inf')
        for k in range(i, -1, -1):
            # Correct: maintaining the minimum width across all rows
            min_width = min(min_width, consecutive_ones[k][j])
            total_submatrices += min_width

3. Off-by-One Errors in Loop Boundaries

When iterating through possible top rows (variable k), it's crucial to include row 0. Using range(i, 0, -1) instead of range(i, -1, -1) would miss all submatrices that extend to the first row.

Incorrect approach:

for k in range(i, 0, -1):  # Misses row 0
    min_width = min(min_width, consecutive_ones[k][j])
    total_submatrices += min_width

Correct approach:

for k in range(i, -1, -1):  # Includes row 0
    min_width = min(min_width, consecutive_ones[k][j])
    total_submatrices += min_width

4. Not Handling Edge Cases Properly

Failing to handle the first column correctly when building the consecutive ones array can lead to index out of bounds errors.

Incorrect approach:

for i in range(rows):
    for j in range(cols):
        if mat[i][j] == 1:
            # Wrong: always accessing j-1, causes error when j=0
            consecutive_ones[i][j] = 1 + consecutive_ones[i][j - 1]

Correct approach:

for i in range(rows):
    for j in range(cols):
        if mat[i][j] == 1:
            # Correct: special handling for j=0
            consecutive_ones[i][j] = 1 if j == 0 else 1 + consecutive_ones[i][j - 1]
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 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

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

Load More