Square of Zeroes
Square of Zeroes
Category
Dynamic Programming
Difficulty
Very Hard
Problem Statement
Given an n x n matrix of 0s and 1s, determine whether there exists a square whose border is made entirely of 0s. The square does not need to be at the boundary of the matrix; it can be located anywhere. Return true if such a square exists with side length >= 2, false otherwise.
Intuition
Checking every possible square naively and verifying its border would take O(n^4) time (O(n^3) squares times O(n) to verify each border). We can precompute for every cell the number of consecutive zeroes going downward and to the right. With this information, verifying whether a given top-left corner can anchor a square of a certain size becomes an O(1) operation: check that the top-left corner has enough zeroes going right and down, and that the top-right and bottom-left corners have enough zeroes going down and right, respectively.
Approach
- Precompute prefix sums of consecutive zeroes:
- Build
zeroesBelow[i][j]: number of consecutive 0s starting at(i, j)going downward. - Build
zeroesRight[i][j]: number of consecutive 0s starting at(i, j)going rightward. - Compute these by scanning from the bottom-right corner upward and leftward.
- Build
- Check all possible squares:
- For each cell
(r, c)as the potential top-left corner:- For each possible side length
sizefrom 2 tomin(n - r, n - c):- Top-left
(r, c): needszeroesRight >= sizeandzeroesBelow >= size. - Top-right
(r, c + size - 1): needszeroesBelow >= size. - Bottom-left
(r + size - 1, c): needszeroesRight >= size. - If all conditions hold, a valid square of zeroes exists; return
true.
- Top-left
- For each possible side length
- For each cell
- If no valid square is found, return
false.
Pseudocode
function squareOfZeroes(matrix):
n = length(matrix)
// Precompute consecutive zeroes going down
zeroesBelow = 2D array of size n x n
for c from 0 to n-1:
zeroesBelow[n-1][c] = (1 - matrix[n-1][c])
for r from n-2 down to 0:
if matrix[r][c] == 0:
zeroesBelow[r][c] = zeroesBelow[r+1][c] + 1
else:
zeroesBelow[r][c] = 0
// Precompute consecutive zeroes going right
zeroesRight = 2D array of size n x n
for r from 0 to n-1:
zeroesRight[r][n-1] = (1 - matrix[r][n-1])
for c from n-2 down to 0:
if matrix[r][c] == 0:
zeroesRight[r][c] = zeroesRight[r][c+1] + 1
else:
zeroesRight[r][c] = 0
// Check all possible squares
for r from 0 to n-1:
for c from 0 to n-1:
maxSize = min(n - r, n - c)
for size from 2 to maxSize:
topLeft = zeroesRight[r][c] >= size and zeroesBelow[r][c] >= size
topRight = zeroesBelow[r][c + size - 1] >= size
botLeft = zeroesRight[r + size - 1][c] >= size
if topLeft and topRight and botLeft:
return true
return false
Time & Space Complexity
- Time: O(n^3). Precomputation is O(n^2). The triple-nested loop iterates over O(n^2) top-left corners and up to O(n) sizes each, giving O(n^3). Each check is O(1).
- Space: O(n^2) for the two precomputed tables.
Key Insights
- Precomputing "consecutive zeroes in a direction" transforms an O(n) border-check into an O(1) lookup.
- Only four corner checks are needed to validate the entire border, because the consecutive-zero counts inherently cover all cells along the four sides.
- The problem asks about the border only, not the interior of the square; interior values are irrelevant.
- You can optimize further with binary search on size for each top-left corner, but the improvement is minor in practice.
Edge Cases
- 1x1 matrix: No square of side length >= 2 is possible, return
false. - 2x2 matrix of all zeroes: The entire matrix border is zeroes, return
true. - Matrix of all ones: No border of zeroes exists, return
false. - Matrix of all zeroes: The largest possible square (the entire matrix) qualifies.
- Zeroes only on one row or column: Cannot form a 2D square border, return
false. - Single zero surrounded by ones: Cannot form a square border of size >= 2.