Count Square Submatrices with All Ones
MedPrompt
Given a m * n matrix of ones and zeros, return how many square submatrices have all ones.
Example 1:
Input: matrix =
[
[0,1,1,1],
[1,1,1,1],
[0,1,1,1]
]
Output: 15
Explanation:
There are 10 squares of side 1.
There are 4 squares of side 2.
There is 1 square of side 3.
Total number of squares = 10 + 4 + 1 = 15.Example 2:
Input: matrix =
[
[1,0,1],
[1,1,0],
[1,1,0]
]
Output: 7
Explanation:
There are 6 squares of side 1.
There is 1 square of side 2.
Total number of squares = 6 + 1 = 7.
Constraints:
1 <= arr.length <= 3001 <= arr[0].length <= 3000 <= arr[i][j] <= 1
Approaches
4 approaches with complexity analysis and trade-offs.
The brute-force approach is the most straightforward way to solve the problem. It involves systematically checking every possible submatrix to see if it's a square composed entirely of ones. We can iterate through every cell of the matrix, considering it as the top-left corner of a potential square. From each top-left corner, we then try to form squares of increasing side lengths (1x1, 2x2, 3x3, etc.) and for each size, we verify if all its elements are ones. If they are, we increment our total count.
Algorithm
- Initialize a counter
countto 0. - Get the dimensions of the matrix,
m(rows) andn(columns). - Iterate through each cell
(r, c)from(0, 0)to(m-1, n-1). This cell will serve as the potential top-left corner of a square. - For each cell
(r, c), ifmatrix[r][c]is 1, start checking for squares of increasing sizesstarting froms=1. - The maximum possible side length
sfor a square starting at(r, c)ismin(m-r, n-c). - For each size
s, check if thes x ssubmatrix starting at(r, c)is composed entirely of ones.- This involves a nested loop from
i = rtor + s - 1andj = ctoc + s - 1. - If any element
matrix[i][j]is 0, then it's not a valid square of ones. We can stop checking for larger sizes from this(r, c)and break the loop overs.
- This involves a nested loop from
- If the
s x ssubmatrix is all ones, increment thecount. - After all iterations, return the total
count.
Walkthrough
This method iterates through all possible top-left corners (r, c) of a square. For each corner, it then iterates through all possible side lengths s. For each combination of (r, c, s), it checks if the corresponding s x s submatrix contains only ones. A helper function or nested loops can perform this check. If the check passes, a counter is incremented. To add a small optimization, if a square of size s starting at (r, c) is found to not be all ones, we know that no square of size s+1 or larger starting at the same corner can be all ones, so we can stop checking for larger sizes from that corner.
class Solution { public int countSquares(int[][] matrix) { if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return 0; } int m = matrix.length; int n = matrix[0].length; int count = 0; for (int r = 0; r < m; r++) { for (int c = 0; c < n; c++) { if (matrix[r][c] == 1) { int maxSide = Math.min(m - r, n - c); for (int s = 1; s <= maxSide; s++) { if (isAllOnes(matrix, r, c, s)) { count++; } else { // If a square of size s is not all ones, // no larger square from this corner can be all ones. break; } } } } } return count; } private boolean isAllOnes(int[][] matrix, int r, int c, int s) { for (int i = r; i < r + s; i++) { for (int j = c; j < c + s; j++) { if (matrix[i][j] == 0) { return false; } } } return true; }}Complexity
Time
O(m * n * min(m, n)^3). We have loops for rows (m), columns (n), and side length (min(m,n)). Inside, we check an s x s square, which takes O(s^2) time. A slightly better implementation checks only the new border, leading to O(m * n * min(m, n)^2). In either case, it's a high-degree polynomial complexity.
Space
O(1) extra space, as we are only using a few variables to keep track of the count and loop indices.
Trade-offs
Pros
Simple to conceptualize and implement.
Requires no extra space (O(1) space complexity).
Cons
Extremely inefficient due to multiple nested loops.
The time complexity makes it infeasible for the given constraints, likely resulting in a 'Time Limit Exceeded' error.
This approach uses dynamic programming to solve the problem efficiently. The key idea is to build a DP table (let's call it dp) of the same size as the input matrix. Each cell dp[i][j] will store the side length of the largest square submatrix of all ones whose bottom-right corner is the cell matrix[i][j].
The total number of squares is simply the sum of all values in this dp table. This is because a value k at dp[i][j] implies that there is a k x k square ending at (i, j), which also means there are squares of size (k-1)x(k-1), (k-2)x(k-2), ..., 1x1 ending at the same position. Thus, this cell contributes k squares to the total count.
Algorithm
- Initialize a counter
countto 0. - Get the dimensions of the matrix,
mandn. - Create a new 2D array
dpof sizem x nto store our results.dp[i][j]will store the side length of the largest square of ones with its bottom-right corner atmatrix[i][j]. - Iterate through each cell
(i, j)of the matrix. - If
matrix[i][j]is 1:- If
iorjis 0 (i.e., the cell is in the first row or first column), it can only form a 1x1 square. So,dp[i][j] = 1. - Otherwise, the size of the square ending at
(i, j)is limited by the squares ending at its top(i-1, j), left(i, j-1), and top-left(i-1, j-1)neighbors. The recurrence relation isdp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]).
- If
- The value
dp[i][j]also represents the number of squares that have(i, j)as their bottom-right corner. For example, ifdp[i][j] = 3, it implies there's a 3x3, a 2x2, and a 1x1 square ending at(i, j). So, we adddp[i][j]to our totalcount. - If
matrix[i][j]is 0,dp[i][j]is 0, and we add nothing to the count. - After iterating through all cells, return the total
count.
Walkthrough
We create a dp matrix. For any cell (i, j), if matrix[i][j] is 0, no square can end there, so dp[i][j] is 0. If matrix[i][j] is 1, we can form a square of at least size 1. To form a larger square, say of size k > 1, we must have a (k-1)x(k-1) square ending at (i-1, j), a (k-1)x(k-1) square ending at (i, j-1), and a (k-1)x(k-1) square ending at (i-1, j-1). The size of the square at (i, j) is therefore one plus the minimum of the sizes of these three neighboring squares. We iterate through the matrix, compute each dp[i][j], and add this value to a running total.
class Solution { public int countSquares(int[][] matrix) { if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return 0; } int m = matrix.length; int n = matrix[0].length; int[][] dp = new int[m][n]; int count = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (matrix[i][j] == 1) { if (i == 0 || j == 0) { dp[i][j] = 1; } else { dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])); } count += dp[i][j]; } } } return count; }}Complexity
Time
O(m * n), as we visit each cell of the matrix exactly once to compute its DP value.
Space
O(m * n) to store the DP table.
Trade-offs
Pros
Significantly more efficient time-wise than the brute-force approach.
The logic is a classic and elegant application of dynamic programming.
Cons
Requires extra space proportional to the size of the input matrix, which can be significant for large matrices.
Observing the recurrence relation dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]), we can see that to compute the values for the current row i, we only need information from the previous row i-1 and the current row i itself. This means we don't need to store the entire m x n DP table. We can optimize the space complexity by only keeping track of the DP values for the previous row.
Algorithm
- Initialize
count = 0. - Get matrix dimensions
mandn. - Instead of a 2D DP table, create a 1D array
dpof sizen. This array will store the DP values of the previous row. - Iterate through the rows of the matrix from
i = 0tom-1. - For each row, create a new 1D array
currentRowDpof sizento store the DP values for the current row. - Iterate through the columns from
j = 0ton-1. - If
matrix[i][j] == 1:- If
i == 0orj == 0,currentRowDp[j] = 1. - Otherwise,
currentRowDp[j] = 1 + min(dp[j-1], dp[j], currentRowDp[j-1]).dp[j-1]is the top-left neighbor from the previous row's DP values.dp[j]is the top neighbor from the previous row's DP values.currentRowDp[j-1]is the left neighbor from the current row's DP values.
- If
- Add
currentRowDp[j]to the totalcount. - After the inner loop (columns) finishes, update the
dparray for the next iteration:dp = currentRowDp. - Return
count.
Walkthrough
We can use a 1D array, say dp, of size n to store the DP values of the row we just processed. When computing the values for the current row, we use this dp array for the dp[i-1] values. A second array currentRowDp can be used to build the results for the current row. After the row is processed, currentRowDp becomes the dp array for the next iteration. A further optimization can use a single 1D array and a variable prev to store the diagonal element dp[i-1][j-1], reducing space to a single O(n) array.
class Solution { public int countSquares(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; int[] dp = new int[n]; int count = 0; for (int j = 0; j < n; j++) { if (matrix[0][j] == 1) { dp[j] = 1; count++; } } for (int i = 1; i < m; i++) { int prev = dp[0]; // This will be the top-left for the next element (j=1) if (matrix[i][0] == 1) { dp[0] = 1; count++; } else { dp[0] = 0; } for (int j = 1; j < n; j++) { int temp = dp[j]; // Store the original dp[j] (top neighbor) if (matrix[i][j] == 1) { // dp[j] is top, dp[j-1] is left (already updated for current row), prev is top-left dp[j] = 1 + Math.min(prev, Math.min(dp[j], dp[j - 1])); count += dp[j]; } else { dp[j] = 0; } prev = temp; // Update prev for the next iteration } } return count; }}Complexity
Time
O(m * n), as we still iterate through each cell of the matrix once.
Space
O(n), where n is the number of columns. We only need to store the DP values for the previous row.
Trade-offs
Pros
Maintains the optimal O(m*n) time complexity.
Reduces space complexity significantly from O(m*n) to O(n).
Cons
The logic can be slightly more complex to manage compared to the standard 2D DP approach, especially when trying to optimize to a single 1D array and a variable.
This is the most space-efficient version of the dynamic programming solution. It reuses the input matrix to store the DP table, thereby eliminating the need for any significant extra space. The logic remains the same as the standard DP approach. The value at matrix[i][j] is updated to store the side length of the largest square of ones ending at that cell. This is possible because when we compute the value for matrix[i][j], the original values of its top, left, and top-left neighbors are no longer needed for subsequent calculations; their already-computed DP values are what matter.
Algorithm
- Initialize a counter
countto 0. - Get the dimensions of the matrix,
mandn. - Iterate through each cell
(i, j)of the input matrix itself. - If
matrix[i][j]is 1:- If the cell is not in the first row or first column (i.e.,
i > 0andj > 0):- Update the value in the input matrix to store the DP result:
matrix[i][j] = 1 + min(matrix[i-1][j], matrix[i][j-1], matrix[i-1][j-1]).
- Update the value in the input matrix to store the DP result:
- The value
matrix[i][j]now represents the side length of the largest square ending at(i, j), which is also the number of squares ending at this cell. - Add the current value of
matrix[i][j]to the totalcount.
- If the cell is not in the first row or first column (i.e.,
- If
matrix[i][j]was originally 0, it remains 0 and contributes nothing to the count. - After iterating through all cells, return the total
count.
Walkthrough
We traverse the matrix from top-left to bottom-right. For each cell (i, j), if its value is 1, we update it based on its neighbors. The cells in the first row and first column serve as the base cases; if matrix[i][j] is 1, its DP value is 1. For any other cell (i, j) where matrix[i][j] is 1, we update it to be 1 + min(matrix[i-1][j], matrix[i][j-1], matrix[i-1][j-1]). The crucial insight is that by the time we process (i, j), the values at (i-1, j), (i, j-1), and (i-1, j-1) have already been converted to their final DP values. We sum up these new values as we compute them to get the total count.
class Solution { public int countSquares(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; int count = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (matrix[i][j] == 1) { if (i > 0 && j > 0) { matrix[i][j] = 1 + Math.min(matrix[i - 1][j - 1], Math.min(matrix[i - 1][j], matrix[i][j - 1])); } count += matrix[i][j]; } } } return count; }}Complexity
Time
O(m * n), as we iterate through each cell of the matrix once.
Space
O(1) extra space, as all calculations are done in-place on the input matrix.
Trade-offs
Pros
Optimal time complexity of O(m*n).
Optimal space complexity of O(1) (no extra space besides the input).
Very concise implementation.
Cons
This approach modifies the input matrix. If the original matrix needs to be preserved, a copy must be made first, which would negate the space savings and make it equivalent to the standard O(m*n) space DP approach.
Solutions
Solution
class Solution {public int countSquares(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; int[][] f = new int[m][n]; int ans = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (matrix[i][j] == 0) { continue; } if (i == 0 || j == 0) { f[i][j] = 1; } else { f[i][j] = Math.min(f[i - 1][j - 1], Math.min(f[i - 1][j], f[i][j - 1])) + 1; } ans += f[i][j]; } } return ans; }}function countSquares ( matrix ) { const [ m , n ] = [ matrix . length , matrix [ 0 ]. length ]; const f = Array . from ({ length : m }, () => Array ( n )); const dfs = ( i , j ) => { if ( i === m || j === n || ! matrix [ i ][ j ]) return 0 ; f [ i ][ j ] ??= 1 + Math . min ( dfs ( i + 1 , j ), dfs ( i , j + 1 ), dfs ( i + 1 , j + 1 )); return f [ i ][ j ]; }; let ans = 0 ; for ( let i = 0 ; i < m ; i ++ ) { for ( let j = 0 ; j < n ; j ++ ) { ans += dfs ( i , j ); } } return ans ; }class Solution {public: int countSquares(vector<vector<int>> &matrix) { int m = matrix.size(), n = matrix[0].size(); int ans = 0; vector<vector<int>> f(m, vector<int>(n)); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (matrix[i][j] == 0) continue; if (i == 0 || j == 0) f[i][j] = 1; else f[i][j] = min(f[i - 1][j - 1], min(f[i - 1][j], f[i][j - 1])) + 1; ans += f[i][j]; } } return ans; }};class Solution: def countSquares(self, matrix: List[List[int]]) -> int: m, n = len(matrix), len(matrix[0]) f = [[0] * n for _ in range(m)] ans = 0 for i, row in enumerate(matrix): for j, v in enumerate(row): if v == 0: continue if i == 0 or j == 0: f[i][j] = 1 else: f[i][j] = min(f[i - 1][j - 1], f[i - 1][j], f[i][j - 1]) + 1 ans += f[i][j] return ansVideo 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.