Set Matrix Zeroes
Given an m x n integer matrix, if an element is 0, set its entire row and column to 0's. You must do it in place.
- m == matrix.length
- n == matrix[0].length
- 1 <= m, n <= 200
- -2³¹ <= matrix[i][j] <= 2³¹ - 1
matrix = [[1,1,1],[1,0,1],[1,1,1]][[1,0,1],[0,0,0],[1,0,1]]matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]][[0,0,0,0],[0,4,5,0],[0,3,1,0]]The task sounds mechanical — find the zeros, wipe their rows and columns — but it hides a genuine trap. Try the naive plan: scan the grid, and every time you meet a 0, immediately zero out its row and column. The zeros you write are now indistinguishable from the zeros that were originally there. The scan then treats your freshly written zeros as new triggers, wiping their rows and columns too, and the chain reaction can flatten the whole grid. The real problem is therefore: separate discovery from writing — first learn where the original zeros are, only then modify anything.
Notice we do not need to remember each zero's exact position — only which rows and which columns are condemned. One boolean per row, one per column:
rows_to_zero = [False] * m
cols_to_zero = [False] * n
for i in range(m): # pass 1: discovery only, no writes
for j in range(n):
if matrix[i][j] == 0:
rows_to_zero[i] = True
cols_to_zero[j] = True
for i in range(m): # pass 2: writes only, no discovery
for j in range(n):
if rows_to_zero[i] or cols_to_zero[j]:
matrix[i][j] = 0Correct, O(M×N) time, and O(M + N) extra space for the flags. The interview follow-up is predictable by now: do it with O(1) extra space.
The flags need one bit per row and one per column — and the matrix already has a column (the first cell of every row) and a row (the first cell of every column) we could write those bits into: mark "row i is condemned" by setting matrix[i][0] = 0, and "column j is condemned" by setting matrix[0][j] = 0.
But that destroys the original values of the first row and column — is that safe? Yes, and here is the argument: we only ever write a marker into matrix[i][0] when row i contains a zero — meaning every cell of row i, including matrix[i][0] itself, is destined to become 0 in the final answer anyway. The same holds for matrix[0][j]. We only overwrite information whose final value is already decided. Nothing the future needs is lost.
One corner needs care: cell (0, 0) is shared — a 0 there is ambiguous between "row 0 is condemned" and "column 0 is condemned". The fix costs one boolean variable (still O(1)): track the first column's fate separately.
The first row and column are now the notebook. If pass 2 zeroes them before the interior is processed, the markers are wiped while still needed. So: zero the interior first, the notebook last — the code below walks rows bottom-up so row 0 (the marker row) is processed at the very end, and handles column 0 after each row's interior.
m, n = len(matrix), len(matrix[0])
first_col_zero = False
for i in range(m): # pass 1: record into the notebook
if matrix[i][0] == 0: first_col_zero = True
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = 0 # condemn row i
matrix[0][j] = 0 # condemn column j
for i in range(m - 1, -1, -1): # pass 2: interior first, notebook last
for j in range(n - 1, 0, -1):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
if first_col_zero:
matrix[i][0] = 0Still O(M×N) time — every cell is read and possibly written — but the extra space fell from O(M+N) to O(1). Two lessons travel well beyond this problem. First, when an in-place transformation risks confusing its own writes with its input, split the work into a read-only discovery pass and a write-only execution pass. Second, when you need scratch memory you are not allowed to allocate, hunt for cells whose final value is already determined — they are free storage, because overwriting them destroys nothing the future needs.
State Matrix Check-Sheet
Scanning matrix for zeroes. Found a 0 at (Row 1, Col 1).
Memory Trick
Instead of an extra O(M+N) array, we use the matrix's own cell [0][0] and the first row/column to store whether that row or col should be zeroed.
Complexity
Time: O(M*N) for two passes. Space: O(1) constant extra space. This demonstrates mastery of bit-flag like optimizations in matrices.