221. Maximal Square - Explanation
Description
You are given an m x n binary matrix filled with 0's and 1's, find the largest square containing only 1's and return its area.
Example 1:
Input: matrix = [
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
Output: 4Example 2:
Input: matrix = [
["0","1"],
["1","0"]
]
Output: 1Constraints:
1 <= matrix.length, matrix[i].length <= 300matrix[i][j]is'0'or'1'.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- 2D Arrays/Matrices - Traversing and manipulating elements in a grid structure
- Dynamic Programming - Building solutions from smaller subproblems and using recurrence relations
- Space Optimization - Reducing space complexity by recognizing that only previous row/column values are needed
1. Brute Force
Intuition
For each cell containing a '1', we can try to expand a square outward as far as possible. Starting with a 1x1 square, we incrementally check if we can form a 2x2, 3x3, and so on. For each size, we only need to verify the new rightmost column and bottommost row that would be added. If any cell in those edges is '0', we cannot expand further. This approach checks all potential squares explicitly.
Algorithm
- Iterate through each cell
(r, c)in the matrix. - If
matrix[r][c]is'0', skip it. - Otherwise, try expanding a square of side
kstarting at1:- Check if
r + kandc + kare within bounds. - Verify all cells in the new right column and bottom row are
'1'. - If any cell is
'0', stop expanding. - Update the result with
k * kif the square is valid.
- Check if
- Return the maximum area found.
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
m, n = len(matrix), len(matrix[0])
res = 0
for r in range(m):
for c in range(n):
if matrix[r][c] == "0":
continue
k = 1
while True:
if r + k > m or c + k > n:
break
flag = True
for i in range(r, r + k):
if matrix[i][c + k - 1] == "0":
flag = False
break
for j in range(c, c + k):
if matrix[r + k - 1][j] == "0":
flag = False
break
if not flag:
break
res = max(res, k * k)
k += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number columns.
2. Dynamic Programming (Top-Down)
Intuition
We can define a recursive function where dp(r, c) returns the side length of the largest square whose top-left corner is at (r, c). For a cell with '1', the answer depends on how far we can extend to the right, down, and diagonally. The limiting factor is the minimum of these three directions. We memoize results to avoid redundant computation, then scan all cells to find the maximum value.
Algorithm
- Create a memoization table initialized to
-1(unvisited). - Define
dfs(r, c):- If out of bounds, return
0. - If already computed, return the cached value.
- Recursively compute
dfs(r+1, c),dfs(r, c+1), anddfs(r+1, c+1). - If
matrix[r][c]is'1', setdp[r][c] = 1 + min(down, right, diag). - Otherwise, set
dp[r][c] = 0.
- If out of bounds, return
- Call
dfs(0, 0)to fill the table. - Find the maximum value in the table and return its square.
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
ROWS, COLS = len(matrix), len(matrix[0])
cache = {}
def dfs(r, c):
if r >= ROWS or c >= COLS:
return 0
if (r, c) not in cache:
down = dfs(r + 1, c)
right = dfs(r, c + 1)
diag = dfs(r + 1, c + 1)
cache[(r, c)] = 0
if matrix[r][c] == "1":
cache[(r, c)] = 1 + min(down, right, diag)
return cache[(r, c)]
dfs(0, 0)
return max(cache.values()) ** 2Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number columns.
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of recursion, we can fill the DP table iteratively from bottom-right to top-left. For each '1' cell, the largest square ending there (as the bottom-right corner) is determined by the minimum of the squares ending at its right, bottom, and diagonal neighbors, plus one. This builds up from smaller subproblems to larger ones and avoids recursion overhead.
Algorithm
- Create a DP table of size
(m+1) x (n+1)initialized to0. - Iterate from
r = m-1down to0, andc = n-1down to0:- If
matrix[r][c]is'1', setdp[r][c] = 1 + min(dp[r+1][c], dp[r][c+1], dp[r+1][c+1]). - Update
maxSquarewithdp[r][c].
- If
- Return
maxSquare * maxSquare.
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
m, n = len(matrix), len(matrix[0])
dp = [[0] * (n + 1) for _ in range(m + 1)]
max_square = 0
for r in range(m - 1, -1, -1):
for c in range(n - 1, -1, -1):
if matrix[r][c] == "1":
dp[r][c] = 1 + min(dp[r + 1][c], dp[r][c + 1], dp[r + 1][c + 1])
max_square = max(max_square, dp[r][c])
return max_square * max_squareTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number columns.
4. Dynamic Programming (Space Optimized)
Intuition
The bottom-up DP only needs the current row and the next row to compute values. We can reduce space by using a single 1D array. As we process each row from right to left, we keep track of the previous diagonal value in a variable prev. This allows us to update the array in place while still having access to all three neighbors needed for the recurrence.
Algorithm
- Create a 1D DP array of size
n+1initialized to0. - Iterate from
r = m-1down to0:- Set
prev = 0(represents the diagonal value from the previous iteration). - For each column
cfromn-1down to0:- Store
dp[c]intemp(this will be the next diagonal). - If
matrix[r][c]is'1', setdp[c] = 1 + min(dp[c], dp[c+1], prev)and updatemaxSquare. - Otherwise, set
dp[c] = 0. - Update
prev = temp.
- Store
- Set
- Return
maxSquare * maxSquare.
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
m, n = len(matrix), len(matrix[0])
dp = [0] * (n + 1)
max_square = 0
for r in range(m - 1, -1, -1):
prev = 0
for c in range(n - 1, -1, -1):
temp = dp[c]
if matrix[r][c] == "1":
dp[c] = 1 + min(dp[c], dp[c + 1], prev)
max_square = max(max_square, dp[c])
else:
dp[c] = 0
prev = temp
return max_square * max_squareTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number columns.
Common Pitfalls
Returning the Side Length Instead of the Area
The DP table stores the side length of the largest square, but the problem asks for the area. Forgetting to square the maximum side length before returning gives an incorrect answer.
Comparing Characters Instead of Character Values
Matrix elements are characters ('0' and '1'), not integers. Comparing against integer 0 or 1 instead of character '0' or '1' causes the condition to always evaluate incorrectly, resulting in wrong DP values.
Incorrect DP Recurrence Direction
The recurrence dp[r][c] = 1 + min(dp[r+1][c], dp[r][c+1], dp[r+1][c+1]) assumes processing from bottom-right to top-left. Processing in the wrong direction (top-left to bottom-right without adjusting the formula) references uncomputed values and produces incorrect results.
Sign in to join the discussion