125. Set Matrix Zeroes
Problem
Modify a rectangular integer matrix in place so every row and column containing an original zero becomes entirely zero. New zeros created during the operation must not trigger additional rows or columns. Return the same matrix; empty input is unchanged.
Examples
Example 1
set_zeroes([[1,1,1],[1,0,1],[1,1,1]])
=> [[1, 0, 1], [0, 0, 0], [1, 0, 1]]Only the original center row and column propagate.
Example 2
set_zeroes([[0,1],[2,3]])
=> [[0, 0], [0, 3]]The two saved boundary flags are both needed.
Example 3
set_zeroes([])
=> []No markers are needed.
Reveal the worked solution and reasoning
Why this mechanism works
The first row and first column can store markers for the rest of the matrix, but their original zero status must be saved separately. Discover all markers before applying them. This separates observation of original state from propagation of new state.
State and boundaries
first_row and first_col remember original boundary-zero flags. Interior zeros mark matrix[r][0] and matrix[0][c].
Protocol
- Save the first row and column flags.
- Mark rows and columns from original interior zeros.
- Zero interior cells indicated by markers.
- Apply the saved first-row and first-column flags last.
Visualization
original center zero
mark its row at first column
mark its column at first row
apply markers only after discovery is completePython implementation
def set_zeroes(matrix):
if not matrix or not matrix[0]:
return matrix
rows, cols = len(matrix), len(matrix[0])
first_row = any(value == 0 for value in matrix[0])
first_col = any(row[0] == 0 for row in matrix)
for r in range(1, rows):
for c in range(1, cols):
if matrix[r][c] == 0:
matrix[r][0] = matrix[0][c] = 0
for r in range(1, rows):
for c in range(1, cols):
if matrix[r][0] == 0 or matrix[0][c] == 0:
matrix[r][c] = 0
if first_row:
for c in range(cols):
matrix[0][c] = 0
if first_col:
for r in range(rows):
matrix[r][0] = 0
return matrix
Worked execution
set_zeroes([[1,1,1],[1,0,1],[1,1,1]])
L2 set_zeroes: if not matrix or not matrix[0]: matrix=[[1, 1, 1], [1, 0, 1], [1, 1, 1]] L4 set_zeroes: rows, cols = len(matrix), len(matrix[0]) matrix=[[1, 1, 1], [1, 0, 1], [1, 1, 1]]; rows=3; cols=3 L5 <genexpr>: first_row = any(value == 0 for value in matrix[0]) .0=list_iterator; value=1 L5 <genexpr>: first_row = any(value == 0 for value in matrix[0]) .0=list_iterator; value=1 L5 <genexpr>: first_row = any(value == 0 for value in matrix[0]) .0=list_iterator; value=1 L5 <genexpr>: first_row = any(value == 0 for value in matrix[0]) .0=list_iterator; value=1 L5 set_zeroes: first_row = any(value == 0 for value in matrix[0]) matrix=[[1, 1, 1], [1, 0, 1], [1, 1, 1]]; rows=3; cols=3; first_row=False L6 <genexpr>: first_col = any(row[0] == 0 for row in matrix) .0=list_iterator; row=[1, 1, 1] ... 15 intermediate state changes omitted ... L11 set_zeroes: for r in range(1, rows): matrix=[[1, 0, 1], [0, 0, 0], [1, 1, 1]]; rows=3; cols=3; first_row=False; first_col=False; r=2; c=2 L12 set_zeroes: for c in range(1, cols): matrix=[[1, 0, 1], [0, 0, 0], [1, 1, 1]]; rows=3; cols=3; first_row=False; first_col=False; r=2; c=1 L14 set_zeroes: matrix[r][c] = 0 matrix=[[1, 0, 1], [0, 0, 0], [1, 0, 1]]; rows=3; cols=3; first_row=False; first_col=False; r=2; c=1 L12 set_zeroes: for c in range(1, cols): matrix=[[1, 0, 1], [0, 0, 0], [1, 0, 1]]; rows=3; cols=3; first_row=False; first_col=False; r=2; c=2
Result: [[1, 0, 1], [0, 0, 0], [1, 0, 1]]. Only the original center row and column propagate.
set_zeroes([[0,1],[2,3]])
L2 set_zeroes: if not matrix or not matrix[0]: matrix=[[0, 1], [2, 3]] L4 set_zeroes: rows, cols = len(matrix), len(matrix[0]) matrix=[[0, 1], [2, 3]]; rows=2; cols=2 L5 <genexpr>: first_row = any(value == 0 for value in matrix[0]) .0=list_iterator; value=0 L5 set_zeroes: first_row = any(value == 0 for value in matrix[0]) matrix=[[0, 1], [2, 3]]; rows=2; cols=2; first_row=True L6 <genexpr>: first_col = any(row[0] == 0 for row in matrix) .0=list_iterator; row=[0, 1] L6 set_zeroes: first_col = any(row[0] == 0 for row in matrix) matrix=[[0, 1], [2, 3]]; rows=2; cols=2; first_row=True; first_col=True L7 set_zeroes: for r in range(1, rows): matrix=[[0, 1], [2, 3]]; rows=2; cols=2; first_row=True; first_col=True; r=1 L8 set_zeroes: for c in range(1, cols): matrix=[[0, 1], [2, 3]]; rows=2; cols=2; first_row=True; first_col=True; r=1; c=1 ... 2 intermediate state changes omitted ... L17 set_zeroes: matrix[0][c] = 0 matrix=[[0, 0], [2, 3]]; rows=2; cols=2; first_row=True; first_col=True; r=1; c=1 L19 set_zeroes: for r in range(rows): matrix=[[0, 0], [2, 3]]; rows=2; cols=2; first_row=True; first_col=True; r=0; c=1 L19 set_zeroes: for r in range(rows): matrix=[[0, 0], [2, 3]]; rows=2; cols=2; first_row=True; first_col=True; r=1; c=1 L20 set_zeroes: matrix[r][0] = 0 matrix=[[0, 0], [0, 3]]; rows=2; cols=2; first_row=True; first_col=True; r=1; c=1
Result: [[0, 0], [0, 3]]. The two saved boundary flags are both needed.
Why the solution is correct
Marker discovery reads only original interior cells and writes only boundary cells, so it cannot create new interior evidence. The markers therefore identify exactly the required rows and columns. Applying them after discovery, then restoring boundary behavior from saved flags, implements precisely the original-zero rule.
Time and space
O(rows*cols) time and O(1) auxiliary space.
Common mistakes
Zeroing immediately while scanning causes cascades from newly written zeros. Using matrix[0][0] alone cannot independently remember both boundary flags.
Reasoning card
State: first_row and first_col remember original boundary-zero flags. Interior zeros mark matrix[r][0] and matrix[0][c].
Invariant: Markers reflect only zeros from the original state.
Decision to rehearse: Record markers without letting newly written zeros create extra effects.
Finish: Apply the saved first-row and first-column flags last
Transfer exercise
Build a simpler O(rows+cols)-space reference and use it as an independent oracle for the marker implementation.