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.
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
, theng[i][j] = 0
(remains initialized value) - If
mat[i][j] = 1
and it's the first column (j = 0
), theng[i][j] = 1
- If
mat[i][j] = 1
and it's not the first column, theng[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:
- Update
col = min(col, g[k][j])
- this maintains the minimum width available from rowk
to rowi
- 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
- Bottom-right corner at
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 EvaluatorExample 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:
-
First nested loop (preprocessing): Iterates through all
m × n
elements to compute the consecutive 1s extending left from each position. This takesO(m × n)
time. -
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 rowi
up to row0
, which is at mostm
iterations - Inside the innermost loop, we perform constant time operations (min comparison and addition)
- Total:
O(m × n × m) = O(m² × n)
- The outer two loops iterate through all positions
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 sizem × 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]
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
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Want a Structured Path to Master System Design Too? Don’t Miss This!