Count Submatrices With All Ones
MedPrompt
Given an m x n binary matrix mat, return the number of submatrices that have all ones.
Example 1:
Input: mat = [[1,0,1],[1,1,0],[1,1,0]]
Output: 13
Explanation:
There are 6 rectangles of side 1x1.
There are 2 rectangles of side 1x2.
There are 3 rectangles of side 2x1.
There is 1 rectangle of side 2x2.
There is 1 rectangle of side 3x1.
Total number of rectangles = 6 + 2 + 3 + 1 + 1 = 13.Example 2:
Input: mat = [[0,1,1,0],[0,1,1,1],[1,1,1,0]]
Output: 24
Explanation:
There are 8 rectangles of side 1x1.
There are 5 rectangles of side 1x2.
There are 2 rectangles of side 1x3.
There are 4 rectangles of side 2x1.
There are 2 rectangles of side 2x2.
There are 2 rectangles of side 3x1.
There is 1 rectangle of side 3x2.
Total number of rectangles = 8 + 5 + 2 + 4 + 2 + 2 + 1 = 24.
Constraints:
1 <= m, n <= 150mat[i][j]is either0or1.
Approaches
4 approaches with complexity analysis and trade-offs.
The most straightforward approach is to generate every possible submatrix and check if it contains all ones. A submatrix can be defined by its top-left corner (r1, c1) and its bottom-right corner (r2, c2). We can use four nested loops to iterate through all possible combinations of these corners. For each submatrix, we then perform another nested iteration to verify that all its elements are 1. If they are, we increment a counter.
Algorithm
- Initialize a counter
countto 0. - Use four nested loops to iterate through all possible top-left
(r1, c1)and bottom-right(r2, c2)corners of submatrices. - For each submatrix defined by these corners, create a helper function
isAllOnes. - The
isAllOnesfunction iterates through every cell from(r1, c1)to(r2, c2). - If it finds any cell with a value of
0, it immediately returnsfalse. - If the loops complete without finding any
0, it returnstrue. - If
isAllOnesreturnstrue, increment thecount. - After all possible submatrices are checked, return the final
count.
Walkthrough
This method exhaustively checks every single submatrix within the given matrix mat. The process is as follows:
- We iterate through each possible starting row
r1from0tom-1. - For each
r1, we iterate through each possible starting columnc1from0ton-1. - This pair
(r1, c1)defines the top-left corner of a potential submatrix. - Then, for each top-left corner, we iterate through all possible ending rows
r2fromr1tom-1. - And for each
r2, we iterate through all possible ending columnsc2fromc1ton-1. - This
(r2, c2)pair defines the bottom-right corner. - Once a submatrix is defined, we must verify if it's composed entirely of ones. A helper function can do this by iterating from row
r1tor2and columnc1toc2. If a0is found, the submatrix is invalid. If the entire submatrix is traversed without finding a0, it's a valid all-ones submatrix, and we increment our total count.
class Solution { public int numSubmat(int[][] mat) { int m = mat.length; int n = mat[0].length; int count = 0; for (int r1 = 0; r1 < m; r1++) { for (int c1 = 0; c1 < n; c1++) { for (int r2 = r1; r2 < m; r2++) { for (int c2 = c1; c2 < n; c2++) { if (isAllOnes(mat, r1, c1, r2, c2)) { count++; } } } } } return count; } private boolean isAllOnes(int[][] mat, int r1, int c1, int r2, int c2) { for (int i = r1; i <= r2; i++) { for (int j = c1; j <= c2; j++) { if (mat[i][j] == 0) { return false; } } } return true; }}Complexity
Time
O(m³ * n³)
Space
O(1)
Trade-offs
Pros
Simple to conceptualize and implement.
Requires no extra space.
Cons
Extremely inefficient due to six nested loops in total.
Will result in a 'Time Limit Exceeded' error for the given constraints.
The brute-force approach is slow because the check for an all-ones submatrix takes O(m*n) time. We can optimize this check to O(1) using a 2D prefix sum array. We first precompute a matrix where each cell (i, j) stores the sum of all elements in the rectangle from (0, 0) to (i, j). With this, the sum of any submatrix can be found in constant time. We then iterate through all possible submatrices, and if a submatrix's sum equals its area, we count it.
Algorithm
- Create a 2D prefix sum matrix
prefixSumof size(m+1) x (n+1). - Populate
prefixSum[i+1][j+1]with the sum of all elements in the rectangle from(0,0)to(i,j)in the original matrix. - The formula is:
prefixSum[i+1][j+1] = mat[i][j] + prefixSum[i][j+1] + prefixSum[i+1][j] - prefixSum[i][j]. - Iterate through all possible top-left
(r1, c1)and bottom-right(r2, c2)corners of submatrices. - For each submatrix, calculate its sum in
O(1)time using theprefixSummatrix. - Calculate the area of the submatrix:
area = (r2 - r1 + 1) * (c2 - c1 + 1). - If the sum equals the area, it means the submatrix is composed of all ones. Increment the total count.
- Return the total count.
Walkthrough
This approach improves upon the pure brute-force method by optimizing the verification step. The key idea is to pre-calculate sums of rectangular regions of the matrix.
- Precomputation: We create a
prefixSummatrix of size(m+1) x (n+1).prefixSum[i][j]will store the sum of elements inmatfrom(0,0)to(i-1, j-1). This can be computed inO(m*n)time. - Verification: Once the
prefixSummatrix is built, the sum of any submatrix defined by(r1, c1)and(r2, c2)can be calculated inO(1)time. The sum isprefixSum[r2+1][c2+1] - prefixSum[r1][c2+1] - prefixSum[r2+1][c1] + prefixSum[r1][c1]. - Counting: We still iterate through all
O(m^2 * n^2)possible submatrices. For each one, we calculate its sum and its area. Ifsum == area, it must be an all-ones submatrix, so we increment our counter.
While the check is now O(1), the overall complexity is dominated by iterating through all submatrices.
class Solution { public int numSubmat(int[][] mat) { int m = mat.length; int n = mat[0].length; int[][] prefixSum = new int[m + 1][n + 1]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { prefixSum[i + 1][j + 1] = mat[i][j] + prefixSum[i][j + 1] + prefixSum[i + 1][j] - prefixSum[i][j]; } } int count = 0; for (int r1 = 0; r1 < m; r1++) { for (int c1 = 0; c1 < n; c1++) { for (int r2 = r1; r2 < m; r2++) { for (int c2 = c1; c2 < n; c2++) { int sum = prefixSum[r2 + 1][c2 + 1] - prefixSum[r1][c2 + 1] - prefixSum[r2 + 1][c1] + prefixSum[r1][c1]; int area = (r2 - r1 + 1) * (c2 - c1 + 1); if (sum == area) { count++; } } } } } return count; }}Complexity
Time
O(m² * n²)
Space
O(m * n) for the prefix sum matrix.
Trade-offs
Pros
Much faster than the pure brute-force approach.
Introduces the useful concept of prefix sums.
Cons
The time complexity is still too high for the given constraints.
Requires O(m*n) extra space for the prefix sum matrix.
A more efficient approach is to re-frame the problem. Instead of considering every submatrix, we can iterate through each cell (i, j) and count how many all-ones submatrices have this cell as their bottom-right corner.
We can process the matrix row by row. For each row i, we can think of it as the base of a histogram, where the height of the bar at column j is the number of consecutive 1s above mat[i][j] (inclusive). Then, for each bar (i, j) in this histogram, we can find how many rectangles can be formed with (i, j) as the bottom-right point by looking leftwards and considering the minimum height of the bars encountered.
Algorithm
- Initialize
count = 0and an auxiliary arrayheightof sizento all zeros. - Iterate through each row
iof the matrix from top to bottom. - For each row
i, update theheightarray. For each columnj,height[j]becomesheight[j] + 1ifmat[i][j] == 1, andheight[j]is reset to0ifmat[i][j] == 0. Thisheightarray represents a histogram of consecutive ones ending at the current row. - After updating
heightfor rowi, iterate through each columnjfrom0ton-1. - For each
j, ifheight[j] > 0, start a backward loop forkfromjdown to0. - In the inner loop, maintain a
minHeightwhich is the minimum height in the histogram from columnktoj. - Add this
minHeightto the totalcount. The loop forkstops ifheight[k]becomes0. - After iterating through all rows, return the total
count.
Walkthrough
This dynamic programming approach builds the solution row by row.
- We maintain an array
heightof sizen, which stores the heights of consecutive1s for the current row, forming a histogram. - We iterate through each row
ifrom0tom-1. - For each row, we first update the
heightarray. For each columnj, ifmat[i][j]is1, we incrementheight[j]. If it's0, we resetheight[j]to0. - After updating the
heightarray for rowi, we calculate the number of new submatrices that have their bottom edge on this row. - We do this by iterating through each column
jfrom0ton-1. For eachj, we consider it as the right edge of potential rectangles. We then iterate backwards fromk = jto0. In this inner loop, we keep track of the minimum height (minHeight) in the range[k, j]. The number of rectangles with bottom-right corner at(i, j)and bottom-left corner at(i, k)is equal to thisminHeight. We add this to our total count.
This reduces the complexity significantly compared to brute-force methods.
class Solution { public int numSubmat(int[][] mat) { int m = mat.length; int n = mat[0].length; int count = 0; int[] height = new int[n]; for (int i = 0; i < m; i++) { // Update heights for the current row's histogram for (int j = 0; j < n; j++) { if (mat[i][j] == 1) { height[j]++; } else { height[j] = 0; } } // Calculate submatrices ending at row i for (int j = 0; j < n; j++) { int minHeight = height[j]; for (int k = j; k >= 0 && height[k] > 0; k--) { minHeight = Math.min(minHeight, height[k]); count += minHeight; } } } return count; }}Complexity
Time
O(m * n²)
Space
O(n) for the auxiliary height array.
Trade-offs
Pros
Much more efficient than brute-force approaches.
Passes the time limits for the given constraints.
The space complexity can be optimized to O(n).
Cons
The triple nested loop structure suggests it might not be the most optimal solution, although it's efficient enough to pass.
This approach optimizes the O(m*n²) solution to O(m*n). The bottleneck in the previous approach was the inner loop that iterated backward from j to calculate the contribution of each bar in the histogram. This calculation can be optimized. For each row's histogram, we can compute the number of rectangles ending at each column j in amortized O(1) time using a dynamic programming recurrence aided by a monotonic stack. The stack helps efficiently find the 'previous smaller element' for each bar, which is key to the DP recurrence.
Algorithm
- Initialize
totalCount = 0and aheightarray of sizen. - Iterate through each row
ifrom0tom-1. - Update the
heightarray for the current row as in the previous approach. - For the current row's
heighthistogram, calculate the number of submatrices using a DP approach with a monotonic stack. a. Initialize adparray of sizenand an emptystack(to store indices). b. Iteratejfrom0ton-1: i. While the stack is not empty and the height of the bar at the index on top of the stack is greater than or equal toheight[j], pop from the stack. ii. Find the indexpof the previous smaller bar. If the stack is now empty,p = -1. Otherwise,pis the index at the top of the stack. iii. Calculatedp[j], the number of rectangles ending at columnj, using the recurrence:dp[j] = dp[p] + (j - p) * height[j]. (Ifpis -1,dp[p]is 0). iv. Push the current indexjonto the stack. c. Sum all values in thedparray and add this sum tototalCount. - Return
totalCount.
Walkthrough
This is the most optimal solution, building upon the histogram-based approach. For each row, we compute the height array as before. The core optimization is in how we count the rectangles for that histogram.
Let dp[j] be the number of all-ones submatrices whose bottom-right corner is at (i, j). We can establish a recurrence for dp[j] based on dp values of previous columns in the same row.
The recurrence is: dp[j] = dp[p] + (j - p) * height[j], where p is the index of the first bar to the left of j that is strictly shorter than the bar at j (height[p] < height[j]).
-
Why this recurrence works: The rectangles ending at
(i, j)can be split into two groups. The first group consists of rectangles that are taller thanheight[p]. Their widths are at mostj-p. For all these rectangles,height[j]is not the minimum height. The second group consists of rectangles that are shorter than or equal toheight[p]. The number of such rectangles is exactlydp[p]. The number of rectangles in the first group is(j-p) * height[j]. Summing them gives the recurrence. -
Monotonic Stack: To find the index
p(previous smaller element) for eachjefficiently, we use a monotonic stack that stores indices of bars with increasing heights. As we iterate throughj, we pop from the stack all indices of bars that are taller than or equal to the current barheight[j]. The index at the top of the stack after this process is ourp.
This allows us to calculate the contributions for an entire row in O(n) time, leading to an overall O(m*n) solution.
import java.util.Stack; class Solution { public int numSubmat(int[][] mat) { int m = mat.length; int n = mat[0].length; int totalCount = 0; int[] height = new int[n]; for (int i = 0; i < m; i++) { // 1. Update heights for the current row to form a histogram for (int j = 0; j < n; j++) { height[j] = (mat[i][j] == 1) ? height[j] + 1 : 0; } // 2. Calculate submatrices for the histogram of the current row int[] dp = new int[n]; // dp[j] = number of submatrices with bottom-right at (i, j) Stack<Integer> stack = new Stack<>(); // stores indices of increasing heights for (int j = 0; j < n; j++) { while (!stack.isEmpty() && height[stack.peek()] >= height[j]) { stack.pop(); } int p = stack.isEmpty() ? -1 : stack.peek(); int prev_dp = (p == -1) ? 0 : dp[p]; dp[j] = prev_dp + (j - p) * height[j]; stack.push(j); } // 3. Add the sum of dp array to the total count int rowSum = 0; for(int val : dp) { rowSum += val; } totalCount += rowSum; } return totalCount; }}Complexity
Time
O(m * n)
Space
O(n) for the height array, DP array, and the stack.
Trade-offs
Pros
Optimal time complexity.
Efficiently solves the problem within the given constraints.
Uses a powerful combination of DP and the monotonic stack pattern.
Cons
The logic, especially the DP recurrence and its connection to the monotonic stack, is more complex to derive and understand.
Solutions
Solution
class Solution {public int numSubmat(int[][] mat) { int m = mat.length, n = mat[0].length; int[][] g = new int[m][n]; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (mat[i][j] == 1) { g[i][j] = j == 0 ? 1 : 1 + g[i][j - 1]; } } } int ans = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { int col = 1 << 30; for (int k = i; k >= 0 && col > 0; --k) { col = Math.min(col, g[k][j]); ans += col; } } } return ans; }}class Solution: def numSubmat(self, mat: List[List[int]]) -> int: m, n = len(mat), len(mat[0]) g = [[0] * n for _ in range(m)] 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] ans = 0 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 return ansclass Solution {public: int numSubmat(vector<vector<int>> &mat) { int m = mat.size(), n = mat[0].size(); vector<vector<int>> g(m, vector<int>(n)); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (mat[i][j] == 1) { g[i][j] = j == 0 ? 1 : 1 + g[i][j - 1]; } } } int ans = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { int col = 1 << 30; for (int k = i; k >= 0 && col > 0; --k) { col = min(col, g[k][j]); ans += col; } } } return ans; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.