73. Set Matrix Zeroes
Problem Description
You are given an m x n
integer matrix. Your task is to modify the matrix such that if any element in the matrix is 0
, then you must set its entire row and entire column to 0
s.
The key requirement is that you must perform this operation in place, meaning you should modify the original matrix directly without using additional matrix space.
For example, if you have a matrix like:
1 1 1 1 0 1 1 1 1
After applying the operation, it should become:
1 0 1 0 0 0 1 0 1
The second row and second column are all set to 0
because there was a 0
at position [1][1]
.
The solution uses two boolean arrays row
and col
to track which rows and columns need to be zeroed. First, it scans through the entire matrix to identify all positions containing 0
, marking the corresponding rows and columns. Then, it makes a second pass through the matrix, setting any element to 0
if its row or column was marked.
The time complexity is O(m × n)
where m
is the number of rows and n
is the number of columns, as we traverse the matrix twice. The space complexity is O(m + n)
for the two marking arrays.
Intuition
The challenge here is that we need to set entire rows and columns to 0
, but we can't do it immediately when we find a 0
because that would interfere with our scanning process. If we start setting zeros right away, we might lose information about which zeros were originally in the matrix versus which ones we just added.
For instance, if we find a 0
at position [1][1]
and immediately set the entire row and column to 0
, when we continue scanning and reach position [1][2]
, we'll see a 0
there (which we just set), but we won't know if it was originally 0
or not. This could lead to incorrectly zeroing out additional rows and columns.
The key insight is to separate the detection phase from the modification phase. We need to first identify all the rows and columns that need to be zeroed, then apply the changes in a second pass.
Think of it like marking items on a checklist before taking action. We need to:
- First, walk through the entire matrix and make a note of which rows and columns contain at least one
0
- Then, go through the matrix again and actually perform the zeroing operation based on our notes
This leads us to use two auxiliary arrays: row[i]
tells us whether row i
should be zeroed, and col[j]
tells us whether column j
should be zeroed. During the first pass, whenever we encounter matrix[i][j] == 0
, we mark row[i] = True
and col[j] = True
. In the second pass, for each position [i][j]
, if either row[i]
or col[j]
is True
, we set matrix[i][j] = 0
.
This two-pass approach ensures we preserve all the original information we need before making any modifications, avoiding the problem of losing track of which zeros were original.
Solution Approach
Following the array marking strategy, we implement the solution using two boolean arrays to track which rows and columns need to be zeroed.
Step 1: Initialize marking arrays
m, n = len(matrix), len(matrix[0])
row = [False] * m
col = [False] * n
We create two boolean arrays: row
of size m
(number of rows) and col
of size n
(number of columns). Initially, all values are False
, meaning no rows or columns are marked for zeroing yet.
Step 2: First pass - Mark rows and columns
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
row[i] = col[j] = True
We traverse the entire matrix once. Whenever we encounter a 0
at position [i][j]
, we mark both:
row[i] = True
to indicate that rowi
contains at least one zerocol[j] = True
to indicate that columnj
contains at least one zero
This marking process preserves the information about all original zeros in the matrix without modifying the matrix itself yet.
Step 3: Second pass - Apply the zeros
for i in range(m):
for j in range(n):
if row[i] or col[j]:
matrix[i][j] = 0
We traverse the matrix again. For each position [i][j]
, we check:
- If
row[i]
isTrue
(meaning rowi
should be zeroed), OR - If
col[j]
isTrue
(meaning columnj
should be zeroed)
If either condition is true, we set matrix[i][j] = 0
.
The beauty of this approach is that it cleanly separates the detection phase from the modification phase, ensuring we don't lose any information about the original zeros in the matrix. The use of separate marking arrays makes the logic straightforward and easy to understand.
Time Complexity: O(m × n)
- We traverse the matrix twice
Space Complexity: O(m + n)
- We use two auxiliary arrays of sizes m
and n
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate how the solution works step by step.
Initial Matrix:
[1, 2, 3, 4] [5, 0, 7, 8] [9, 10, 11, 12]
Step 1: Initialize marking arrays
m = 3
(rows),n = 4
(columns)row = [False, False, False]
col = [False, False, False, False]
Step 2: First pass - Mark rows and columns
We scan through each element:
- Position
[0][0]
: value is1
(not zero), continue - Position
[0][1]
: value is2
(not zero), continue - Position
[0][2]
: value is3
(not zero), continue - Position
[0][3]
: value is4
(not zero), continue - Position
[1][0]
: value is5
(not zero), continue - Position
[1][1]
: value is0
→ Markrow[1] = True
andcol[1] = True
- Position
[1][2]
: value is7
(not zero), continue - Position
[1][3]
: value is8
(not zero), continue - Position
[2][0]
: value is9
(not zero), continue - Position
[2][1]
: value is10
(not zero), continue - Position
[2][2]
: value is11
(not zero), continue - Position
[2][3]
: value is12
(not zero), continue
After first pass:
row = [False, True, False]
(row 1 contains a zero)col = [False, True, False, False]
(column 1 contains a zero)
Step 3: Second pass - Apply the zeros
Now we check each position and set to zero if its row or column is marked:
- Position
[0][0]
:row[0] = False
,col[0] = False
→ Keep value1
- Position
[0][1]
:row[0] = False
,col[1] = True
→ Set to0
- Position
[0][2]
:row[0] = False
,col[2] = False
→ Keep value3
- Position
[0][3]
:row[0] = False
,col[3] = False
→ Keep value4
- Position
[1][0]
:row[1] = True
,col[0] = False
→ Set to0
- Position
[1][1]
:row[1] = True
,col[1] = True
→ Set to0
- Position
[1][2]
:row[1] = True
,col[2] = False
→ Set to0
- Position
[1][3]
:row[1] = True
,col[3] = False
→ Set to0
- Position
[2][0]
:row[2] = False
,col[0] = False
→ Keep value9
- Position
[2][1]
:row[2] = False
,col[1] = True
→ Set to0
- Position
[2][2]
:row[2] = False
,col[2] = False
→ Keep value11
- Position
[2][3]
:row[2] = False
,col[3] = False
→ Keep value12
Final Matrix:
[1, 0, 3, 4] [0, 0, 0, 0] [9, 0, 11, 12]
The entire row 1 and column 1 have been set to zero, as required. Note how the two-pass approach ensures we don't accidentally zero out additional rows or columns by preserving the original zero locations in our marking arrays before making any modifications.
Solution Implementation
1class Solution:
2 def setZeroes(self, matrix: List[List[int]]) -> None:
3 """
4 Modify the matrix in-place to set entire rows and columns to 0
5 if any element in that row or column is 0.
6
7 Args:
8 matrix: 2D list of integers to be modified in-place
9 """
10 # Get dimensions of the matrix
11 num_rows, num_cols = len(matrix), len(matrix[0])
12
13 # Track which rows and columns need to be zeroed
14 rows_to_zero = [False] * num_rows
15 cols_to_zero = [False] * num_cols
16
17 # First pass: identify all rows and columns that contain zeros
18 for row_idx in range(num_rows):
19 for col_idx in range(num_cols):
20 if matrix[row_idx][col_idx] == 0:
21 # Mark this row and column for zeroing
22 rows_to_zero[row_idx] = True
23 cols_to_zero[col_idx] = True
24
25 # Second pass: set elements to zero based on marked rows and columns
26 for row_idx in range(num_rows):
27 for col_idx in range(num_cols):
28 # If current row or column is marked, set element to zero
29 if rows_to_zero[row_idx] or cols_to_zero[col_idx]:
30 matrix[row_idx][col_idx] = 0
31
1class Solution {
2 /**
3 * Sets entire rows and columns to zero if an element is zero.
4 * Uses two boolean arrays to track which rows and columns contain zeros.
5 *
6 * @param matrix The input matrix to be modified in-place
7 */
8 public void setZeroes(int[][] matrix) {
9 // Get matrix dimensions
10 int rows = matrix.length;
11 int cols = matrix[0].length;
12
13 // Arrays to track which rows and columns should be set to zero
14 boolean[] zeroRows = new boolean[rows];
15 boolean[] zeroCols = new boolean[cols];
16
17 // First pass: identify all rows and columns that contain zeros
18 for (int i = 0; i < rows; i++) {
19 for (int j = 0; j < cols; j++) {
20 if (matrix[i][j] == 0) {
21 // Mark this row and column to be zeroed out
22 zeroRows[i] = true;
23 zeroCols[j] = true;
24 }
25 }
26 }
27
28 // Second pass: set elements to zero based on marked rows and columns
29 for (int i = 0; i < rows; i++) {
30 for (int j = 0; j < cols; j++) {
31 // If current row or column is marked, set element to zero
32 if (zeroRows[i] || zeroCols[j]) {
33 matrix[i][j] = 0;
34 }
35 }
36 }
37 }
38}
39
1class Solution {
2public:
3 void setZeroes(vector<vector<int>>& matrix) {
4 // Get matrix dimensions
5 int rowCount = matrix.size();
6 int colCount = matrix[0].size();
7
8 // Create boolean arrays to track which rows and columns contain zeros
9 vector<bool> rowHasZero(rowCount, false);
10 vector<bool> colHasZero(colCount, false);
11
12 // First pass: identify all rows and columns that contain at least one zero
13 for (int row = 0; row < rowCount; ++row) {
14 for (int col = 0; col < colCount; ++col) {
15 if (matrix[row][col] == 0) {
16 // Mark this row and column as containing a zero
17 rowHasZero[row] = true;
18 colHasZero[col] = true;
19 }
20 }
21 }
22
23 // Second pass: set matrix elements to zero based on marked rows and columns
24 for (int row = 0; row < rowCount; ++row) {
25 for (int col = 0; col < colCount; ++col) {
26 // If current row or column was marked, set element to zero
27 if (rowHasZero[row] || colHasZero[col]) {
28 matrix[row][col] = 0;
29 }
30 }
31 }
32 }
33};
34
1/**
2 * Sets entire rows and columns to zero if an element is zero.
3 * Modifies the matrix in-place without returning anything.
4 *
5 * @param matrix - The input matrix to be modified
6 */
7function setZeroes(matrix: number[][]): void {
8 // Get matrix dimensions
9 const rowCount: number = matrix.length;
10 const columnCount: number = matrix[0].length;
11
12 // Track which rows contain zeros
13 const rowsWithZeros: boolean[] = Array(rowCount).fill(false);
14
15 // Track which columns contain zeros
16 const columnsWithZeros: boolean[] = Array(columnCount).fill(false);
17
18 // First pass: identify all positions with zeros
19 for (let rowIndex: number = 0; rowIndex < rowCount; rowIndex++) {
20 for (let columnIndex: number = 0; columnIndex < columnCount; columnIndex++) {
21 if (matrix[rowIndex][columnIndex] === 0) {
22 // Mark this row and column as containing a zero
23 rowsWithZeros[rowIndex] = true;
24 columnsWithZeros[columnIndex] = true;
25 }
26 }
27 }
28
29 // Second pass: set zeros based on marked rows and columns
30 for (let rowIndex: number = 0; rowIndex < rowCount; rowIndex++) {
31 for (let columnIndex: number = 0; columnIndex < columnCount; columnIndex++) {
32 // If current row or column was marked, set element to zero
33 if (rowsWithZeros[rowIndex] || columnsWithZeros[columnIndex]) {
34 matrix[rowIndex][columnIndex] = 0;
35 }
36 }
37 }
38}
39
Time and Space Complexity
Time Complexity: O(m × n)
The algorithm consists of two nested loops that iterate through the entire matrix:
- The first nested loop scans all
m × n
elements to identify which rows and columns contain zeros, takingO(m × n)
time. - The second nested loop iterates through all
m × n
elements again to set the appropriate cells to zero based on the row and column markers, also takingO(m × n)
time. - The total time complexity is
O(m × n) + O(m × n) = O(m × n)
.
Space Complexity: O(m + n)
The algorithm uses two auxiliary arrays:
row
: A boolean array of sizem
to track which rows contain at least one zero.col
: A boolean array of sizen
to track which columns contain at least one zero.- The total extra space used is
O(m) + O(n) = O(m + n)
.
Where m
and n
are the number of rows and columns of the matrix respectively.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Modifying the matrix during the first scan
The Problem: A common mistake is attempting to set zeros immediately when finding a zero element during the first scan:
# INCORRECT approach
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
# Setting zeros immediately - DON'T DO THIS!
for k in range(n):
matrix[i][k] = 0
for k in range(m):
matrix[k][j] = 0
This approach fails because it destroys the original information in the matrix. Once you set a row or column to zero, you can't distinguish between:
- Original zeros that should trigger row/column zeroing
- Newly added zeros from the modification
Example of the issue:
Original: After first zero found: Final (incorrect): 1 0 1 0 0 0 0 0 0 1 1 1 --> 1 0 1 --> 0 0 0 1 1 0 1 0 0 0 0 0
The algorithm would incorrectly zero out the entire matrix!
The Solution: Always use a two-pass approach with separate marking arrays to preserve the original zero positions before making any modifications.
Pitfall 2: Space optimization attempt using O(1) space incorrectly
The Problem: Some might try to achieve O(1) space by using the first row and column as markers, but forget to handle them specially:
# INCORRECT O(1) space attempt
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
matrix[i][0] = 0 # Use first column as row marker
matrix[0][j] = 0 # Use first row as column marker
# This corrupts the information about whether the first row/column
# themselves originally contained zeros
The Solution: If attempting O(1) space optimization, you must:
- First check if the first row and column contain any zeros and store this in separate boolean variables
- Use the first row and column to mark other rows/columns (starting from index 1)
- Process the matrix from bottom-right to top-left, leaving the first row and column for last
- Finally, zero out the first row and column based on the initial boolean flags
The O(m+n) space solution with separate arrays is much cleaner and less error-prone, making it the preferred approach unless space is extremely constrained.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!