Skip to content

73. Set Matrix Zeroes (Medium)

Problem

Given an m × n integer matrix, if a cell is 0, set its entire row and column to 0. Do it in place; try for O(1)O(1) extra space as a follow-up.

Example

  • 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]]

LeetCode 73 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, make a copy

Snapshot the matrix; identify zero rows and columns from the snapshot; then write zeroes back into the original.

def set_zeroes(matrix):
rows, cols = len(matrix), len(matrix[0])
snapshot = [row[:] for row in matrix]
zero_rows = {r for r in range(rows)
if any(snapshot[r][c] == 0 for c in range(cols))}
zero_cols = {c for c in range(cols)
if any(snapshot[r][c] == 0 for r in range(rows))}
for r in range(rows):
for c in range(cols):
if r in zero_rows or c in zero_cols:
matrix[r][c] = 0

The snapshot is what makes it correct: without it, the in-place writes would propagate (“a zero I just wrote would trigger more zero-outs”). With a snapshot, the writes only depend on the original state.

Complexity

  • Time: O(mn)O(m · n).
  • Space: O(mn)O(m · n).

Doesn’t meet the in-place requirement.

Approach 2: Two boolean arrays (O(m+n)O(m + n) space)

Collect which rows and columns contain a 0; then zero them.

def set_zeroes(matrix):
rows, cols = len(matrix), len(matrix[0]) # L1: O(1)
zero_rows = [False] * rows # L2: O(m)
zero_cols = [False] * cols # L3: O(n)
for r in range(rows): # L4: first pass, m·n iterations
for c in range(cols):
if matrix[r][c] == 0:
zero_rows[r] = True # L5: O(1)
zero_cols[c] = True # L6: O(1)
for r in range(rows): # L7: second pass, m·n iterations
for c in range(cols):
if zero_rows[r] or zero_cols[c]:
matrix[r][c] = 0 # L8: O(1)

Where the time goes, line by line

Variables: m = number of rows, n = number of columns.

LinePer-call costTimes executedContribution
L2-L3 (init flag arrays)O(1)O(1)m + nO(m+n)O(m + n)
L4-L6 (first scan)O(1)O(1)m·nO(mn)O(m·n) ← dominates
L7-L8 (second scan)O(1)O(1)m·nO(mn)O(m·n)

Two full passes over the matrix, each O(mn)O(m·n).

Complexity

  • Time: O(mn)O(m · n), driven by L4/L5/L6 and L7/L8 (two full matrix scans).
  • Space: O(m+n)O(m + n) for the flag arrays.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 3: First row and column as markers (O(1)O(1) space, canonical)

Use the first row and column themselves as flag arrays. Track separately whether the first row / first column themselves need zeroing.

def set_zeroes(matrix):
rows, cols = len(matrix), len(matrix[0])
first_row_zero = any(matrix[0][c] == 0 for c in range(cols)) # L1: O(n)
first_col_zero = any(matrix[r][0] == 0 for r in range(rows)) # L2: O(m)
# Use first row/col as markers
for r in range(1, rows): # L3: mark pass (m-1)·(n-1)
for c in range(1, cols):
if matrix[r][c] == 0:
matrix[r][0] = 0 # L4: O(1)
matrix[0][c] = 0 # L5: O(1)
# Zero based on markers
for r in range(1, rows): # L6: apply pass (m-1)·(n-1)
for c in range(1, cols):
if matrix[r][0] == 0 or matrix[0][c] == 0:
matrix[r][c] = 0 # L7: O(1)
if first_row_zero:
for c in range(cols):
matrix[0][c] = 0 # L8: O(n)
if first_col_zero:
for r in range(rows):
matrix[r][0] = 0 # L9: O(m)

Where the time goes, line by line

Variables: m = number of rows, n = number of columns.

LinePer-call costTimes executedContribution
L1-L2 (first row/col check)O(1)O(1)m + nO(m+n)O(m + n)
L3-L5 (mark pass)O(1)O(1)(m-1)·(n-1)O(mn)O(m·n) ← dominates
L6-L7 (apply pass)O(1)O(1)(m-1)·(n-1)O(mn)O(m·n)
L8-L9 (fix first row/col)O(1)O(1)m + nO(m+n)O(m + n)

Three linear passes total; two of them are O(mn)O(m·n), two are O(m+n)O(m + n).

Complexity

  • Time: O(mn)O(m · n), driven by L3/L4/L5 and L6/L7 (two full inner-matrix scans).
  • Space: O(1)O(1) extra.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpace
Copy + overwriteO(mn)O(m · n)O(mn)O(m · n)
Boolean flag arraysO(mn)O(m · n)O(m+n)O(m + n)
First row/col as markersO(mn)O(m · n)O(1)O(1)

“Reuse the input as auxiliary storage” is a recurring in-place trick.

Test cases

# Quick smoke tests, paste into a REPL or save as test_073.py and run.
# Uses the canonical implementation (Approach 3: first row/col as markers).
# set_zeroes() modifies the matrix in place.
def set_zeroes(matrix):
rows, cols = len(matrix), len(matrix[0])
first_row_zero = any(matrix[0][c] == 0 for c in range(cols))
first_col_zero = any(matrix[r][0] == 0 for r in range(rows))
for r in range(1, rows):
for c in range(1, cols):
if matrix[r][c] == 0:
matrix[r][0] = 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_zero:
for c in range(cols):
matrix[0][c] = 0
if first_col_zero:
for r in range(rows):
matrix[r][0] = 0
def _run_tests():
m = [[1,1,1],[1,0,1],[1,1,1]]
set_zeroes(m)
assert m == [[1,0,1],[0,0,0],[1,0,1]]
m2 = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
set_zeroes(m2)
assert m2 == [[0,0,0,0],[0,4,5,0],[0,3,1,0]]
m3 = [[1]] # no zeroes
set_zeroes(m3)
assert m3 == [[1]]
m4 = [[0]] # single zero
set_zeroes(m4)
assert m4 == [[0]]
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Arrays, in-place marking strategy
  • Array Scans, the linear pass habit of carrying just enough state while reading each item once.
  • Simulation, the explicit state model for executing rules exactly while keeping cases organized.