LeetCode 73: Set Matrix Zeroes
Imagine you're playing Minesweeper β you click a cell, and suddenly an entire row and column light up. That's the visual for this problem. When a 0 is found, its whole row and column must become 0. Sounds simple, but the real challenge is doing it without extra space. π₯
π§© The Problem
Given an
m Γ ninteger matrix, if an element is0, set its entire row and column to0s.You must do it in place.
Examples
Example 1:
Input: Output:
βββββ¬ββββ¬ββββ βββββ¬ββββ¬ββββ
β 1 β 1 β 1 β β 1 β 0 β 1 β
βββββΌββββΌββββ€ βββββΌββββΌββββ€
β 1 β 0 β 1 β β β 0 β 0 β 0 β
βββββΌββββΌββββ€ βββββΌββββΌββββ€
β 1 β 1 β 1 β β 1 β 0 β 1 β
βββββ΄ββββ΄ββββ βββββ΄ββββ΄ββββ
The 0 at position (1,1) wipes out row 1 and column 1.
Example 2:
Input: Output:
βββββ¬ββββ¬ββββ¬ββββ βββββ¬ββββ¬ββββ¬ββββ
β 0 β 1 β 2 β 0 β β 0 β 0 β 0 β 0 β
βββββΌββββΌββββΌββββ€ βββββΌββββΌββββΌββββ€
β 3 β 4 β 5 β 2 β β β 0 β 4 β 5 β 0 β
βββββΌββββΌββββΌββββ€ βββββΌββββΌββββΌββββ€
β 1 β 3 β 1 β 5 β β 0 β 3 β 1 β 0 β
βββββ΄ββββ΄ββββ΄ββββ βββββ΄ββββ΄ββββ΄ββββ
Two 0s at (0,0) and (0,3) β row 0 is fully zeroed, columns 0 and 3 are fully zeroed.
Constraints
m == matrix.lengthn == matrix[0].length1 <= m, n <= 200-2Β³ΒΉ <= matrix[i][j] <= 2Β³ΒΉ - 1
Step 1 β Start with the obvious solution
If you see a 0 at (i, j):
- row i must become all
0s - column j must become all
0s
So the most natural idea is:
rows = set()
cols = set()First pass β scan and record:
whenever matrix[i][j] == 0:
add i to rows
add j to cols
Second pass β apply:
if row in rows OR col in cols:
make it 0
class Solution(object):
def setZeroes(self, matrix):
m, n = len(matrix), len(matrix[0])
rows = set()
cols = set()
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
rows.add(i)
cols.add(j)
for i in range(m):
for j in range(n):
if i in rows or j in cols:
matrix[i][j] = 0That gives:
- Time: O(m Γ n)
- Space: O(m + n) β for the two sets
This is already good. β
Step 2 β Interview follow-up: can we do O(1) space?
Now comes the trick.
Instead of extra arrays/sets⦠use the matrix itself as storage.
π‘ The Core Insight
The first row can store β which columns should become zero
The first column can store β which rows should become zero
Let's see it with Example 1:
Original: After marking:
βββββ¬ββββ¬ββββ βββββ¬β0ββ¬ββββ β first row stores column markers
β 1 β 1 β 1 β β 1 β 0 β 1 β
βββββΌββββΌββββ€ βββββΌββββΌββββ€
β 1 β 0 β 1 β β 0 β 0 β 1 β β first col stores row markers
βββββΌββββΌββββ€ βββββΌββββΌββββ€
β 1 β 1 β 1 β β 1 β 1 β 1 β
βββββ΄ββββ΄ββββ βββββ΄ββββ΄ββββ
When we find matrix[1][1] == 0:
- Mark row 1:
matrix[1][0] = 0 - Mark column 1:
matrix[0][1] = 0
Now the first row/column act like markers. Smart, right?
β οΈ But there's a problemβ¦
What if the first row itself originally had a zero? Or the first column?
Because we're repurposing them as marker storage, we would lose that information.
So we separately store:
firstRowZero = False
firstColZero = FalseTwo booleans. That's it. Still O(1) space. π―
π§ Full Thinking Process
Pass 1 β Protect the first row & column
Determine:
- Should the first row become zero?
- Should the first column become zero?
Pass 2 β Mark using inner cells
For every inner cell (i, j) where i >= 1 and j >= 1:
If it's zero β mark its row and column:
matrix[i][0] = 0 β mark the row
matrix[0][j] = 0 β mark the column
Pass 3 β Zero inner cells using markers
If matrix[i][0] == 0 OR matrix[0][j] == 0 β zero it out:
matrix[i][j] = 0
Pass 4 β Handle first row/column last
Apply the saved booleans to zero them out if needed.
β οΈ Order matters! We must handle the first row/column last, otherwise we'd corrupt the markers before using them.
π» Final Solution (O(1) Space)
class Solution(object):
def setZeroes(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: None Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
firstRowZero = False
firstColZero = False
# Pass 1: Check if first row has any zero
for j in range(n):
if matrix[0][j] == 0:
firstRowZero = True
# Pass 1: Check if first column has any zero
for i in range(m):
if matrix[i][0] == 0:
firstColZero = True
# Pass 2: Use first row/column as markers
for i in range(1, m):
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = 0
matrix[0][j] = 0
# Pass 3: Zero inner cells based on markers
for i in range(1, m):
for j in range(1, n):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
# Pass 4: Zero first row if needed
if firstRowZero:
for j in range(n):
matrix[0][j] = 0
# Pass 4: Zero first column if needed
if firstColZero:
for i in range(m):
matrix[i][0] = 0π Let's Walk Through Example 2
Input: [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
Pass 1 β Check first row & column:
First row: [0, 1, 2, 0] β has zero β firstRowZero = True
First col: [0, 3, 1] β has zero β firstColZero = True
Pass 2 β Scan inner cells (iβ₯1, jβ₯1):
All inner cells are non-zero β no markers added
Pass 3 β Zero inner cells:
No markers were set by inner cells, so no inner cells change.
Pass 4 β Handle first row/column:
firstRowZero = True β zero out entire first row
firstColZero = True β zero out entire first column
Result:
βββββ¬ββββ¬ββββ¬ββββ
β 0 β 0 β 0 β 0 β β first row zeroed
βββββΌββββΌββββΌββββ€
β 0 β 4 β 5 β 2 β β first col zeroed
βββββΌββββΌββββΌββββ€
β 0 β 3 β 1 β 5 β β first col zeroed
βββββ΄ββββ΄ββββ΄ββββ
Wait β but the expected output has (1,3) and (2,3) as 0 too!
That's because the original matrix[0][3] == 0 β it's in the first row. Let's re-trace:
Pass 2 β revisited: Inner cells scan from (1,1) to (2,3). None of them are 0. But the first row already has a 0 at column 3.
Pass 4 β firstRowZero: We zero out matrix[0][0..3] β all become 0. But column 3 marker matrix[0][3] was already 0 from the original input!
Actually, the key here: matrix[0][3] == 0 was already there. During Pass 3 we check matrix[0][j] β and matrix[0][3] == 0 means column 3 should be zeroed for inner cells.
Re-tracing Pass 3 properly:
(1,3): matrix[1][0]=3β 0, matrix[0][3]=0 β zero it! matrix[1][3] = 0
(2,3): matrix[2][0]=1β 0, matrix[0][3]=0 β zero it! matrix[2][3] = 0
Final result:
βββββ¬ββββ¬ββββ¬ββββ
β 0 β 0 β 0 β 0 β
βββββΌββββΌββββΌββββ€
β 0 β 4 β 5 β 0 β
βββββΌββββΌββββΌββββ€
β 0 β 3 β 1 β 0 β
βββββ΄ββββ΄ββββ΄ββββ
Matches expected output β
β±οΈ Complexity Analysis
| Approach | Time | Space |
|---|---|---|
| Naive (extra sets) | O(m Γ n) | O(m + n) |
| Optimal (in-place markers) | O(m Γ n) | O(1) β |
Both approaches scan every cell at least once (unavoidable), so time is always O(m Γ n). The difference is purely in space.
π Comparison: O(m+n) vs O(1) Space
| Feature | Set-based (O(m+n)) | In-place markers (O(1)) |
|---|---|---|
| Space | O(m + n) | O(1) β just 2 booleans |
| Code simplicity | Straightforward | Needs careful ordering |
| Edge cases | Simple | First row/col need special handling |
| Interview impression | Good starting point | Shows deep thinking π‘ |
β οΈ Common Mistakes
-
Zeroing the first row/column too early β If you handle them in Pass 3 instead of Pass 4, you'll corrupt the markers and get wrong answers. The order is critical.
-
Forgetting to scan the first row/column separately β The inner loop starts at
(1,1), so the first row and column aren't covered by the marking phase. That's why we needfirstRowZeroandfirstColZero. -
Modifying while scanning β If you zero cells during the same pass you're scanning for zeros, you'll create "cascading zeros" that weren't in the original matrix. Always separate the scan phase from the modify phase.
-
Starting inner loops at 0 instead of 1 β The inner marking and zeroing loops must start at index
1, not0. Starting at0would overwrite the markers themselves.
π§ How to Learn to Think Like This
For matrix problems, ask yourself:
1. What information do I need later?
Here:
- Which rows need to be killed
- Which columns need to be killed
2. Can existing input store that info?
This is the big interview trick β reuse the matrix itself.
Common in:
- Matrix problems
- Linked list problems (e.g., marking visited nodes)
- Array problems (e.g., using negative values as flags)
3. What special cases break the trick?
Here:
- The first row β because we're using it as column storage
- The first column β because we're using it as row storage
So we protect them with two booleans.
π Pattern to Remember
"Use first row/column as state storage"
This is a classic in-place trick. You'll see variations of it in:
- This problem (Set Matrix Zeroes)
- Game of Life (LeetCode 289)
- Any problem asking "modify in-place with O(1) extra space"
The general pattern:
- Protect β save special-case info separately
- Mark β use existing structure as storage
- Apply β use marks to make changes
- Restore β handle the special cases last
π― Interview Tips
-
Start with the O(m+n) solution β Show you understand the problem first. Use sets to track rows and columns. It's clean and correct.
-
Then optimize β When the interviewer asks "can you do better on space?", pivot to the in-place marker approach. This is where you shine.
-
Explain the ordering β The most common follow-up is "why do you handle the first row/column last?" Be ready to explain that doing it early would corrupt your markers.
-
Draw it out β This problem is much easier to explain with a visual. Sketch the matrix on the whiteboard and show the marking step.
-
Know the trade-off β Both solutions are O(mΓn) time. The only difference is space. Make sure to articulate why the in-place approach is better in memory-constrained environments.
