LeetCode #221 Medium

Maximal Square

Maximal Square is LeetCode 221 (Medium). You get an m × n grid called matrix whose cells are the characters '0' and '1'.

  • Find the largest square, with sides along the grid lines, in which every cell is '1'.
  • Return that square's area, not its side length.
  • If the grid holds no '1' at all, the answer is 0.

Both dimensions go up to 300, so the grid can have 90,000 cells. Checking every corner and every size is too slow at that scale; the maximal square LeetCode problem expects one pass over the grid.

Constraints
  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 300
  • matrix[i][j] is '0' or '1'.
dynamic-programmingmatrixgrid
Open on LeetCode ↗
02

Intuition

Checking every square directly means picking a corner, a size, and scanning its cells: O(m·n·min(m,n)²). The maximal square dynamic programming idea makes each cell answer a smaller question: how big is the largest all-1 square whose bottom-right corner is right here?

A square of side k ending at a cell needs squares of side k − 1 ending just above it, just left of it and on its diagonal. So the side at a cell is limited by the weakest of those three neighbours, plus one for the cell itself. Each cell's answer comes from three already-solved ones, so a single pass over the grid finds the largest square.

How to spot this pattern

A grid, a shape that must be solid, and "largest" in the question: define dp on the corner where the shape ends and ask which neighbouring shapes it must contain. Count Square Submatrices with All Ones (1277) uses the identical table and sums it instead of taking the max.

03

Approach

Try it first

Before reading on, fill the dp values for a 3 × 3 block of all 1s by hand. Then change the centre to 0 and see which values change.

1

Define the state on the bottom-right corner

dp[i][j] = side of the largest all-1 square whose bottom-right corner is matrix[i-1][j-1]. Use a table with one extra row and column of zeros, so the first real row and column read 0 from outside the grid instead of needing a special case. In the maximal square Python code the table is built with a list comprehension, [[0] * (cols + 1) for _ in range(rows + 1)], so no two rows share one list.

2

Apply the recurrence

  • matrix[i-1][j-1] == '0' – dp[i][j] = 0, since no square can end on a 0.
  • matrix[i-1][j-1] == '1' – dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]).

Fill rows top to bottom and left to right, so the three neighbours are always ready before the cell that reads them. The grid holds characters, so compare with '1', not the number 1.

3

Track the best side and return its area

Keep best = max(best, dp[i][j]) as you fill the table, since the largest square can end at any cell. The problem asks for area, not side length, so return best * best; returning best itself is a common wrong answer.

4

Why the minimum of three is exact

  • Not larger: if the square here had side k, the cells above, left and diagonal would each end a square of side at least k − 1, so the minimum is at least k − 1.
  • Not smaller: if all three are at least k − 1, their squares together cover the whole k × k block except the corner, which is 1, so a k × k square exists.
5

Reduce to one row, if needed

Each row reads only itself and the row above, so a single array of size n + 1 works. Before overwriting dp[j], its old value is up; save it, because it becomes the diagonal for j + 1. dp[j - 1], already updated, is left. Space drops to O(n).

04

Maximal Square solution in Python | C++ | Java

▶1class Solution:
▶2 def maximalSquare(self, matrix: List[List[str]]) -> int:
▶3 rows, cols = len(matrix), len(matrix[0])
▶4 dp = [[0] * (cols + 1) for _ in range(rows + 1)]
▶5 best = 0
▶6 for i in range(1, rows + 1):
▶7 for j in range(1, cols + 1):
▶8 if matrix[i - 1][j - 1] == "1":
▶9 dp[i][j] = 1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
▶10 best = max(best, dp[i][j])
▶11 return best * best
dppaddingmatrix1010010111111111001000000000000000000every 0 cell: dp = 0
size4 × 5
best0largest side so far
State. dp[i][j] is the side of the largest all-1 square whose bottom-right corner is this cell. A 0 cell can end no square, so it is 0. The extra row and column of zeros on top and left mean the first real row needs no special case.
dppaddingmatrix101001011111111100100000000100000000000up0left0diag→0smallest+ 1 =1side here1new best
cell(0, 0)matrix is 1
dp11 + min(0, 0, 0)
best1area 1
This 1 can only end a 1 × 1 square. All three neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix1010010111111111001000000001010000000000up0left0diag→0smallest+ 1 =1side here1best
cell(0, 2)matrix is 1
dp11 + min(0, 0, 0)
best1area 1
This 1 can only end a 1 × 1 square. All three neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix10100101111111110010000000010100010000001up0left0diag→0smallest+ 1 =1side here1best
cell(1, 0)matrix is 1
dp11 + min(1, 0, 0)
best1area 1
This 1 can only end a 1 × 1 square. The left and diagonal neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix101001011111111100100000000101000101000001up0left0diag→0smallest+ 1 =1side here1best
cell(1, 2)matrix is 1
dp11 + min(1, 0, 0)
best1area 1
This 1 can only end a 1 × 1 square. The left and diagonal neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix1010010111111111001000000001010001011000000up1left1diag→0smallest+ 1 =1side here1best
cell(1, 3)matrix is 1
dp11 + min(0, 1, 1)
best1area 1
This 1 can only end a 1 × 1 square. The up neighbour is 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix10100101111111110010000000010100010111000000up1left0diag→0smallest+ 1 =1side here1best
cell(1, 4)matrix is 1
dp11 + min(0, 1, 0)
best1area 1
This 1 can only end a 1 × 1 square. The up and diagonal neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix101001011111111100100000000101000101110100001up0left0diag→0smallest+ 1 =1side here1best
cell(2, 0)matrix is 1
dp11 + min(1, 0, 0)
best1area 1
This 1 can only end a 1 × 1 square. The left and diagonal neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix1010010111111111001000000001010001011101100000up1left1diag→0smallest+ 1 =1side here1best
cell(2, 1)matrix is 1
dp11 + min(0, 1, 1)
best1area 1
This 1 can only end a 1 × 1 square. The up neighbour is 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix10100101111111110010000000010100010111011100001up1left0diag→0smallest+ 1 =1side here1best
cell(2, 2)matrix is 1
dp11 + min(1, 1, 0)
best1area 1
This 1 can only end a 1 × 1 square. The diagonal neighbour is 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix101001011111111100100000000101000101110111200001up1left1diag→1smallest+ 1 =2side here2new best
cell(2, 3)matrix is 1
dp21 + min(1, 1, 1)
best2area 4
The square ending here can grow only as far as all three neighbours allow: up, left and up-left each end a square, and a bigger square here needs all three to be big. All three are 1, so the side is 2, a new best.
dppaddingmatrix1010010111111111001000000001010001011101112200001up2left1diag→1smallest+ 1 =2side here2best
cell(2, 4)matrix is 1
dp21 + min(1, 2, 1)
best2area 4
The square ending here can grow only as far as all three neighbours allow: up, left and up-left each end a square, and a bigger square here needs all three to be big. The smallest, in red, is 1, so the side is 2.
dppaddingmatrix10100101111111110010000000010100010111011122010001up0left0diag→0smallest+ 1 =1side here2best
cell(3, 0)matrix is 1
dp11 + min(1, 0, 0)
best2area 4
This 1 can only end a 1 × 1 square. The left and diagonal neighbours are 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix101001011111111100100000000101000101110111220100102up0left1diag→0smallest+ 1 =1side here2best
cell(3, 3)matrix is 1
dp11 + min(2, 0, 1)
best2area 4
This 1 can only end a 1 × 1 square. The left neighbour is 0, and a 2 × 2 square here would need a 1 in each of them.
dppaddingmatrix101001011111111100100000000101000101110111220100102side×2side=4area
best side2
area4the question asks for area
Return the area, 4. The largest dp value is 2, at the bottom-right corner of the outlined square. Every cell was visited once with three lookups: O(m·n) time.
05

Common pitfalls

Returning the side instead of the area

✗ Wrong
return best
✓ Right
return best * best

dp stores side lengths. The first example's largest square has side 2 and the expected answer is 4.

Comparing with the integer 1

✗ Wrong
if matrix[i - 1][j - 1] == 1:
✓ Right
if matrix[i - 1][j - 1] == "1":

LeetCode passes characters. "1" == 1 is False in Python, so every cell is skipped and the answer is always 0.

06

Edge cases

Rectangle of 1s, not a square

A 2 × 4 block gives side 2, area 4. The minimum over three neighbours stops the square at the shorter side.

07

Complexity

Time
O(m·n)
Space
O(m·n)
Each cell is computed once from three lookups. The one-row version above keeps the same time with O(n) space.
08

Maximal square vs related grid problems

Three problems that look alike but need different tools.

ProblemShapeMethodTime
Maximal Square (221)square of 1s1 + min(up, left, diag)O(m·n)
Count Square Submatrices (1277)every square of 1ssame table, return the sumO(m·n)
Maximal Rectangle (85)rectangle of 1shistogram per row + monotonic stackO(m·n)
09

Maximal Square FAQ

Can I store the sizes in the matrix itself instead of a dp table?

In Python you can overwrite matrix[i][j] with an int, and it saves the table. In C++ and Java the cells are char, so sizes above 9 would need arithmetic on character codes. Changing the caller's input is also a side effect interviewers often ask you to avoid; the one-row array costs only O(n).

Does the answer change if the grid is not square?

No. The recurrence never assumes m = n; only the upper bound on the side changes, to min(m, n).