Problem Walkthrough

Set Matrix Zeroes LeetCode 73 Deep Dive

Solve LeetCode 73 Set Matrix Zeroes in O(1) extra space by repurposing the first row and first column of the matrix itself as boolean flag arrays — no extra memory needed beyond a single boolean for the first column.

8 min read|

Set Matrix Zeroes

LeetCode 73

Problem Overview: Zero Out Rows and Columns In-Place

LeetCode 73 Set Matrix Zeroes gives you an m x n integer matrix. If any element matrix[i][j] is 0, you must set every element in row i and every element in column j to 0. The transformation must be done in-place — you cannot return a new matrix. The challenge is doing this correctly without letting an element you just zeroed trigger additional zeroing that was not part of the original problem.

The naive pitfall is zeroing as you find zeros. If you zero an entire row when you encounter matrix[2][3] == 0, you then encounter the newly-written zeros in that row, which would incorrectly trigger additional column zeroing. You must separate the detection phase from the zeroing phase: first identify all original zeros, then apply the zeroing.

The follow-up asks you to achieve O(1) extra space — meaning you cannot allocate a separate boolean array to remember which rows and columns need zeroing. The optimal solution turns the matrix against itself, using the first row and first column as the storage for those flags.

  • Input: m x n matrix of integers, 1 <= m, n <= 200, -2^31 <= matrix[i][j] <= 2^31 - 1
  • Output: none — modify matrix in-place
  • If matrix[i][j] == 0, set all of row i and all of column j to 0
  • Follow-up: can you do it with O(1) extra space?
  • Naive approach: O(m+n) extra space using separate boolean arrays
  • Optimal approach: O(1) extra space using the first row and column as flags

O(m+n) Space Approach: Separate Boolean Arrays

The first improvement over the naive in-place approach is to use two separate boolean arrays: zeroRows of size m and zeroCols of size n. In the first pass, scan every element. Whenever matrix[i][j] == 0, set zeroRows[i] = true and zeroCols[j] = true. These arrays record which rows and columns should be zeroed, derived only from the original matrix values.

In the second pass, iterate over every element again. If zeroRows[i] is true or zeroCols[j] is true, set matrix[i][j] = 0. Because the boolean arrays were populated entirely from the first pass, the second pass reflects only the original zeros — not zeros introduced during the second pass itself. The two-pass separation correctly handles the detection-before-zeroing requirement.

This approach is clean, easy to reason about, and runs in O(mn) time with O(m+n) space. It is the recommended solution for most interview settings unless the interviewer specifically asks for O(1) space. Once you have this working, the O(1) trick becomes easier to understand because it reuses the exact same two-pass logic — just with the matrix itself serving as the boolean arrays.

💡

The O(1) Trick: Borrow the Matrix's Own First Row and Column

The O(1) space upgrade keeps the two-pass logic identical but eliminates the external boolean arrays. Instead of zeroRows[i], use matrix[i][0]. Instead of zeroCols[j], use matrix[0][j]. The first column of the matrix becomes zeroRows; the first row becomes zeroCols. The only complication is that matrix[0][0] must serve as both zeroRows[0] and zeroCols[0] — it can only hold one value — so a single external boolean is used to track the first column separately.

O(1) Space Strategy: The Matrix as Its Own Flag Array

The O(1) approach uses matrix[i][0] (the first column) to record whether row i should be zeroed, and matrix[0][j] (the first row) to record whether column j should be zeroed. This works because those positions are just integer cells — you can overwrite them with 0 to signal that a row or column needs zeroing, mirroring what the external boolean arrays stored.

The collision at matrix[0][0] is the central subtlety. That cell sits at the intersection of row 0 and column 0. If you use it to mean "row 0 should be zeroed," it cannot simultaneously mean "column 0 should be zeroed." The standard resolution is to let matrix[0][0] represent the first row flag only, and introduce a single boolean variable firstColZero to represent whether the first column should be zeroed.

Aside from that one boolean, no additional O(m) or O(n) data structures are needed. The algorithm modifies the first row and first column as it scans, then uses those modified values in a second pass to apply zeros throughout the matrix. Finally, it handles the first row and first column themselves, using matrix[0][0] and firstColZero respectively.

  1. 1Initialize firstColZero = any cell in column 0 is 0
  2. 2First pass (rows 1..m-1, cols 1..n-1): if matrix[i][j] == 0, set matrix[i][0] = 0 and matrix[0][j] = 0
  3. 3Also check column 0 separately: if matrix[i][0] was originally 0, firstColZero = true
  4. 4Second pass (rows 1..m-1, cols 1..n-1): if matrix[i][0] == 0 or matrix[0][j] == 0, set matrix[i][j] = 0
  5. 5Handle first row: if matrix[0][0] == 0, zero out entire row 0
  6. 6Handle first column: if firstColZero is true, zero out entire column 0

First Pass: Marking Rows and Columns

The first pass scans every cell from row 1 to m-1 and column 1 to n-1 (skipping the first row and column for now, since those are being repurposed as flag storage). Whenever matrix[i][j] equals 0, write 0 into matrix[i][0] to mark row i and write 0 into matrix[0][j] to mark column j. These writes corrupt the original values of those cells — but that is acceptable because those cells are now flag storage, not original data.

Before or during this scan, you must also handle column 0 itself. Iterate down column 0 (or check it in the same pass): if any matrix[i][0] is already 0 in the original matrix, set firstColZero = true. Similarly, if matrix[0][0] is 0 in the original matrix, that means row 0 needs zeroing. The value matrix[0][0] naturally captures this since it is part of the first row scan.

After the first pass completes, matrix[i][0] encodes "should row i be zeroed?" for rows 1 through m-1, matrix[0][j] encodes "should column j be zeroed?" for columns 1 through n-1, matrix[0][0] encodes "should row 0 be zeroed?", and firstColZero encodes "should column 0 be zeroed?" These four pieces of state contain everything needed for the second pass.

ℹ️

matrix[0][0] Marks the First ROW — the Boolean Marks the First COLUMN

matrix[0][0] is used exclusively as the flag for row 0: if matrix[0][0] == 0 after the first pass, zero out the entire first row. The separate firstColZero boolean is used exclusively as the flag for column 0: if firstColZero is true, zero out the entire first column. Conflating these two — using matrix[0][0] to mean both — is the single most common bug in O(1) implementations. Keep them separate.

Second Pass: Applying Zeros in the Right Order

The second pass applies the stored flags to the interior of the matrix — rows 1 through m-1 and columns 1 through n-1. For each cell matrix[i][j], check if matrix[i][0] == 0 (row i is flagged) or matrix[0][j] == 0 (column j is flagged). If either condition is true, set matrix[i][j] = 0. This correctly zeroes out every row and column indicated by an original zero, without the markers being corrupted mid-pass.

After the interior is zeroed, handle the first row separately. Check matrix[0][0]: if it equals 0, set every element in row 0 to 0. This uses the row-0 flag that was stored at matrix[0][0] during the first pass. Then handle the first column: check firstColZero. If it is true, set every element in column 0 to 0.

The ordering of these final steps is critical. You must zero the interior (rows 1..m-1, cols 1..n-1) before zeroing the first row and first column. If you zero the first row or column first, you overwrite the flag values that the interior pass still needs. Doing the interior last avoids this dependency. Within the final two steps, the order of first row vs. first column does not matter.

  1. 1Interior pass: for i in 1..m-1, for j in 1..n-1: if matrix[i][0]==0 or matrix[0][j]==0, set matrix[i][j]=0
  2. 2First row: if matrix[0][0] == 0, set matrix[0][j] = 0 for all j in 0..n-1
  3. 3First column: if firstColZero is True, set matrix[i][0] = 0 for all i in 0..m-1
  4. 4Do NOT zero first row or column before the interior pass — markers are still needed
  5. 5Time complexity: O(mn) — two full passes over the matrix
  6. 6Space complexity: O(1) — only one boolean variable outside the matrix

Code Walkthrough: Python and Java

In Python: first set firstColZero = any(matrix[i][0] == 0 for i in range(m)). Then scan rows 1..m-1 and cols 1..n-1: if matrix[i][j] == 0, set matrix[i][0] = 0 and matrix[0][j] = 0. Interior second pass: for i in 1..m-1, for j in 1..n-1: if matrix[i][0] == 0 or matrix[0][j] == 0, set matrix[i][j] = 0. Then if matrix[0][0] == 0, zero row 0. Then if firstColZero, zero column 0. The full implementation is about 12 lines.

In Java: boolean firstColZero = false; scan column 0 in the first loop to set firstColZero. Then nested loops for the marking pass. The zeroing pass mirrors the Python logic. Use matrix[0][0] for the first-row flag and firstColZero for the first-column flag. Both Python and Java implementations run in O(mn) time and O(1) space. The algorithmic structure is identical across languages — only the syntax differs.

A common variation initializes firstColZero by checking if matrix[0][0] == 0 before the first pass, then handles the overlap differently. Both approaches are correct; the key invariant is that matrix[0][0] represents the first row and the boolean represents the first column, and the first row/column are zeroed last. Practicing both variants ensures you can reconstruct the solution under interview pressure.

⚠️

Zero the First Row and First Column LAST

Always zero the first row and first column after completing the interior zeroing pass. If you zero row 0 or column 0 before the interior pass, you overwrite the flag markers stored in those cells, causing the interior pass to read corrupted values and zero cells that should not be zeroed. The safe order is: (1) mark using first row/column, (2) zero interior using those marks, (3) zero first row if needed, (4) zero first column if needed.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now