LeetCode #73 Medium

Set Matrix Zeroes

If a cell is 0, set its entire row and column to 0in place.

arraymatrixhash-table
Open on LeetCode ↗
02

Intuition

Mutating as you scan would corrupt later checks, so first record which rows and columns must be zeroed, then apply the changes in a second pass.

How to spot this pattern

The trap is that zeroing as you scan corrupts the very data you're still reading. So the fix is two phases: record which rows and columns are doomed, then apply. Separating detection from mutation is the general lesson for any in-place grid transform where a write can be mistaken for input.

03

Approach

1

Zeroing as you scan corrupts the scan

The tempting move — when you find a 0, immediately zero out its whole row and column — is a trap. Those freshly written zeros are indistinguishable from original zeros, so the rest of the scan mistakes them for triggers and cascades, zeroing far more than it should. The fix is to never let detection and mutation happen at the same time.

2

Separate detection from mutation

Make a first pass that only records which rows and which columns contain at least one original zero, storing their indices in two sets. This pass changes nothing, so there's no cascade — every zero it sees is a real one from the input.

3

Apply the flags in a second pass

Now sweep the grid again and set a cell to 0 if its row index or its column index was flagged. Because the flags were all computed from the original matrix, the result is exactly right regardless of overlap (set membership is idempotent, so a cell at a flagged row and column is just zeroed once). Two passes: O(m·n) time, O(m + n) space for the two sets.

04

Solution & live demo

1class Solution:
2 def setZeroes(self, matrix):
3 rows, cols = set(), set()
4 for i, row in enumerate(matrix):
5 for j, v in enumerate(row):
6 if v == 0:
7 rows.add(i)
8 cols.add(j)
9 for i, row in enumerate(matrix):
10 for j in range(len(row)):
11 if i in rows or j in cols:
12 row[j] = 0
05

Common pitfalls

Zeroing during the scan

✗ Wrong
for i, row in enumerate(matrix):
    for j, v in enumerate(row):
        if v == 0:
            for k in range(len(row)): matrix[i][k] = 0
✓ Right
# phase 1: collect rows/cols
# phase 2: apply

The zeros you write are indistinguishable from original zeros, so they trigger further clearing and the whole matrix cascades to zero. Detection must finish before any mutation starts.

Storing coordinates instead of row and column indices

✗ Wrong
zeros = [(i, j) for ...]
for i, j in zeros: matrix[i][j] = 0
✓ Right
rows, cols = set(), set()
...
if i in rows or j in cols: row[j] = 0

Recording the cell only re-zeroes that one cell. What must be cleared is the entire row and the entire column it sits in, so the indices are what matter.

Assuming the O(1)-space version is required

✗ Wrong
# elaborate first-row/first-column marker scheme
✓ Right
rows, cols = set(), set()

The marker trick reduces space to O(1) but is fiddly and easy to get wrong. Two sets are O(m + n) — usually acceptable — and the interviewer generally wants to see this working first before you optimise.

06

Edge cases

No zeros present

Both sets stay empty, so the second pass changes nothing.

Entire matrix is zero

Every row and column is flagged; the whole matrix stays zero after the second pass.

A zero shared by a flagged row and column

Set membership is idempotent, so overlapping flags simply zero the cell once.

07

Complexity

Time
O(m·n)
Space
O(m + n)
Two passes; the sets store at most one entry per row and column.