To solve this problem efficiently, we need to identify every cell that contains a 0 and then set the entire row and column of that cell to 0. However, doing this naively with extra space (like a separate matrix or two hash sets) increases the space complexity.
The optimal way to solve it in-place is to use the matrix itself as storage. More specifically, we use the first row and first column of the matrix as markers.
Step-by-step Understanding
1. First, we scan the matrix to check whether the first row or first column contain any zeros. We use two boolean flags to remember this because we’ll later overwrite these rows/columns with markers.
2. Next, we traverse the entire matrix (excluding first row and column). If we find a cell with value 0 at mat[i][j]
, we mark its row and column by setting mat[i][0] = 0
and mat[0][j] = 0
.
3. Then we scan the matrix again (excluding the first row and column). If either mat[i][0]
or mat[0][j]
is 0, it means the cell should be zeroed out, so we set mat[i][j] = 0
.
4. Lastly, we update the first row and first column based on our flags. If the first row originally had a 0, we set the entire row to 0. Same for the first column.
Handling Special Cases
- No zero exists: In this case, the matrix remains unchanged after the scan.
- All elements are zero: The matrix remains fully zero as expected.
- Zeros only in first row/column: This is why we track the first row/column with separate flags—so we don't lose this information while using them as markers.
- Empty matrix: If the matrix is empty (i.e., has no rows), we simply do nothing and return it as is.
This technique is efficient because it avoids the need for extra memory and ensures the matrix is modified directly. It operates in O(n × m) time and O(1) space complexity (excluding input/output).