Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Math & Geometry
P2MediumMental model~5 min speedrun

In-Place Markers (First Row/Column Flags, Two States per Cell)

The problem

Set Matrix Zeroes (LC 73)(opens leetcode.com)

You get a grid of integers, matrix, with m rows and n columns. Every 0 that the grid holds at the start wipes out its whole row and its whole column: each cell in that row and in that column must end up as 0. Change matrix itself and return nothing.

Only the zeros that are there at the start count. A cell that becomes 0 because of this rule does not wipe out anything further.

A full copy of the grid (O(m * n) extra memory) or one marker per row and per column (O(m + n)) both work. LeetCode's follow-up asks for more: use only O(1) extra memory.

Example 1
Input:matrix = [[1,1,1],[1,0,1],[1,1,1]]
Output:[[1,0,1],[0,0,0],[1,0,1]]
The only 0 sits in the middle, so the middle row and the middle column become 0. The four corners keep their 1.
111101111

In-Place Marking

matrix ➔ markers in the first row / column or spare bits ➔ apply in a second pass
Spot it when: The input must be changed in place with O(1) extra memory, but each change depends on the input as it was: rows or columns to wipe, or a new state that every neighbour must still read in its old form.
In-Place Marking
Python
1def set_zeroes(matrix: list[list[int]]) -> None:
2 """Set Matrix Zeroes: row 0 and column 0 hold the flags for the other rows and columns.
3 Time: O(M * N) | Space: O(1)
4 """
5 m, n = len(matrix), len(matrix[0])
6 
7 # 1. Save Row 0's and Column 0's Own Zeros First:
8 # Pass 1 below overwrites row 0 and column 0 with flags, so whether
9 # they originally held a zero must be recorded before any flag is written.
10 first_row_zero = any(matrix[0][c] == 0 for c in range(n))
11 first_col_zero = any(matrix[r][0] == 0 for r in range(m))
12 
13 # 2. Mark Flags in Row 0 and Column 0:
14 # A zero found inside the matrix (outside row 0 and column 0) means its
15 # whole row and column end up zero, so that is recorded by zeroing the
16 # row's cell in column 0 and the column's cell in row 0.
17 for r in range(1, m):
18 for c in range(1, n):
19 if matrix[r][c] == 0:
20 matrix[r][0] = 0
21 matrix[0][c] = 0
22 
23 # 3. Apply the Flags to Every Inner Cell:
24 # Any inner cell whose row flag or column flag is set is zeroed; row 0
25 # and column 0 themselves are skipped here because they still need to be read.
26 for r in range(1, m):
27 for c in range(1, n):
28 if matrix[r][0] == 0 or matrix[0][c] == 0:
29 matrix[r][c] = 0
30 
31 # 4. Clear Row 0 and Column 0 Last:
32 # Row 0 and column 0 are cleared only after every other cell has read
33 # their flags; clearing them earlier would turn every flag true and wipe the whole matrix.
34 if first_row_zero:
35 for c in range(n):
36 matrix[0][c] = 0
37 if first_col_zero:
38 for r in range(m):
39 matrix[r][0] = 0
Invariant: Every flag written into row 0 or column 0 marks a row or column that ends up all zero anyway, and first_row_zero / first_col_zero hold row 0's and column 0's own answers from before any flag was written.

When no extra memory is allowed, store the marks in cells whose final value you already know: a zero at (r, c) means row r and column c end up all zero, so pass 1 writes the flags matrix[r][0] = 0 and matrix[0][c] = 0, and pass 2 zeroes every cell whose row flag or column flag is 0. Writing a flag loses nothing, because both flag cells become zero in the answer anyway; the same idea packs two states into one cell when every neighbour must still read it in its old form, because the old value stays readable until every reader is done, whether that means a whole cell repurposed as a flag or one bit of it holding the next state. The trap is that row 0 and column 0 are data as well as flags: compute first_row_zero and first_col_zero before pass 1 writes any flag, keep both passes to r >= 1 and c >= 1, and clear row 0 and column 0 last, because clearing them first turns every flag to 0 and wipes the whole matrix.

See it run

In-Place Marking, step by step

pass start · first_row_zero false · first_col_zero false

pass save · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass clear · first_row_zero true · first_col_zero false

pass clear · first_row_zero true · first_col_zero false

pass done · first_row_zero true · first_col_zero false · answer [[0,0,0,0],[5,0,7,0],[0,0,0,0]]

Each box is one cell of the matrix. Row 0 and column 0 hold the flags for the other rows and columns.

  • scan the inner cell the loop is checking
  • set 0 a cell the code writes as 0 in this step
  • flag a cell in row 0 or column 0 that holds a mark
  • keeps a cell that must stay unchanged
  • saved an original 0 in row 0 or column 0, recorded in first_row_zero or first_col_zero

Start: a 3 by 4 matrix. Row 0 and column 0 will hold the flags, so their own zeros are saved first.

first_row_zero = true and first_col_zero = false, computed before any flag is written. Pass 1 will overwrite row 0 and column 0.

matrix[1][1] is 6, not 0, so pass 1 writes no flag.

matrix[1][2] is 7, not 0, so pass 1 writes no flag.

matrix[1][3] is 8, not 0, so pass 1 writes no flag.

matrix[2][1] is 10, not 0, so pass 1 writes no flag.

matrix[2][2] is 11, not 0, so pass 1 writes no flag.

matrix[2][3] is 0, so set matrix[2][0] and matrix[0][3] to 0 as flags.

Trap: clearing row 0 now would set every matrix[0][c] to 0. Pass 2 would then also clear matrix[1][2] = 7, which must stay. So row 0 is cleared last.

matrix[0][1] is 0, so matrix[1][1] changes from 6 to 0.

matrix[1][0] is 5 and matrix[0][2] is 3, so matrix[1][2] = 7 stays.

matrix[0][3] is 0, so matrix[1][3] changes from 8 to 0.

matrix[2][0] is 0, so matrix[2][1] changes from 10 to 0.

matrix[2][0] is 0, so matrix[2][2] changes from 11 to 0.

matrix[2][0] is 0, so matrix[2][3] changes from 0 to 0.

first_row_zero is True, so row 0 is cleared now, after every other cell has read its flag.

first_col_zero is False, so column 0 keeps its values.

Done: every row and column that held a 0 is now all 0. The matrix is [[0, 0, 0, 0], [5, 0, 7, 0], [0, 0, 0, 0]].

row 0
col 01
col 10
col 23
col 34
row 1
5
6
7
8
row 2
9
10
11
0
1 / 18
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Clearing row 0 before pass 2 reads the flags

    Mistake
    if first_row_zero:
        for c in range(n):
            matrix[0][c] = 0
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:
                matrix[r][c] = 0
    Fix
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:
                matrix[r][c] = 0
    if first_row_zero:
        for c in range(n):
            matrix[0][c] = 0

    What happens: After pass 1, row 0 holds the column flags. A 0 at matrix[0][c] means "clear column c". If row 0 is cleared before pass 2, every matrix[0][c] becomes 0. Pass 2 then finds a flag in every column and clears the whole matrix.

    Example: matrix = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
    Mistake: returns [[0, 0, 0], [0, 0, 0], [0, 0, 0]]Fix: returns [[0, 0, 0], [0, 4, 5], [0, 7, 8]]
  2. 2

    Gotcha 2: Using one flag for both row 0 and column 0

    Mistake
    first_zero = any(matrix[0][c] == 0 for c in range(n)) or any(matrix[r][0] == 0 for r in range(m))
    first_row_zero = first_col_zero = first_zero
    Fix
    first_row_zero = any(matrix[0][c] == 0 for c in range(n))
    first_col_zero = any(matrix[r][0] == 0 for r in range(m))

    What happens: One shared flag can only say that row 0 or column 0 had a 0. It can't say which one. So a 0 only in column 0 also clears all of row 0. Using matrix[0][0] as that one flag has the same problem, because the cell is in both. The blueprint keeps two booleans, first_row_zero and first_col_zero.

    Example: matrix = [[1, 1], [0, 1]]
    Mistake: returns [[0, 0], [0, 0]]Fix: returns [[0, 1], [0, 0]]
  3. 3

    Gotcha 3: Computing first_row_zero after pass 1 writes the flags

    Mistake
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][c] == 0:
                matrix[r][0] = 0
                matrix[0][c] = 0
    first_row_zero = any(matrix[0][c] == 0 for c in range(n))
    Fix
    first_row_zero = any(matrix[0][c] == 0 for c in range(n))
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][c] == 0:
                matrix[r][0] = 0
                matrix[0][c] = 0

    What happens: Pass 1 writes 0s into row 0 as column flags. After that, any(matrix[0][c] == 0 ...) can't tell a flag from a 0 that was in the input. It finds a flag and sets first_row_zero to True. The last step then clears all of row 0.

    Example: matrix = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]
    Mistake: returns [[0, 0, 0], [0, 0, 0], [1, 0, 1]]Fix: returns [[1, 0, 1], [0, 0, 0], [1, 0, 1]]
  4. 4

    Gotcha 4: Zeroing row r and column c during the scan

    Mistake
    if matrix[r][c] == 0:
        for k in range(n):
            matrix[r][k] = 0
        for k in range(m):
            matrix[k][c] = 0
    Fix
    if matrix[r][c] == 0:
        matrix[r][0] = 0
        matrix[0][c] = 0

    What happens: Pass 1 scans the cells in order. If it writes 0s into the row and the column at once, the scan meets those new 0s later. It takes them for 0s that were in the input. Each one clears another row and column. The 0s spread to rows and columns that had none.

    Example: matrix = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]
    Mistake: returns [[1, 0, 0], [0, 0, 0], [0, 0, 0]]Fix: returns [[1, 0, 1], [0, 0, 0], [1, 0, 1]]
  5. 5

    Gotcha 5: Starting pass 2 at row 0 instead of row 1

    Mistake
    for r in range(m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:
    Fix
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:

    What happens: Row 0 holds the column flags until pass 2 ends. If pass 2 starts at row 0 and matrix[0][0] == 0, it sets every matrix[0][c] to 0. Every later row then finds a flag in every column and is cleared.

    Example: matrix = [[0, 1], [1, 1]]
    Mistake: returns [[0, 0], [0, 0]]Fix: returns [[0, 0], [0, 1]]
Not this one if…
  • The values are 1..n and each belongs in its own slot, as in First Missing Positive (LC 41): once a value is home, its old slot content is never needed again, so no mark has to keep the old value readable. See Cyclic Sort

Deep Dive

Why a flag in row 0 or column 0 loses nothing

Set Matrix Zeroes (LC 73) needs to remember which rows and columns contain a 0. The simple way stores two lists, one for the rows and one for the columns, which costs O(M + N) extra space. The card stores the same information inside the matrix, so the extra space is O(1).

A flag must be written in a cell whose final value is already known. If matrix[r][c] is 0, then row r and column c end up all 0. So matrix[r][0] and matrix[0][c] end up 0 in the answer. Writing a 0 there as a flag changes no final value.

The flag still destroys the old value of that cell. This matters only for the zeros that were already in row 0 and column 0. A 0 at matrix[r][0] with r >= 1 already acts as the flag for row r. It also means that column 0 must be cleared, and one boolean records that: first_col_zero. A 0 in row 0 is handled in the same way by first_row_zero. That is why both booleans are computed before pass 1.

The two passes, traced on a 3 by 4 matrix

Take matrix = [[1, 0, 3, 4], [5, 6, 7, 8], [9, 10, 11, 0]]. Row 0 holds a 0 at matrix[0][1], so first_row_zero is True. Column 0 holds no 0, so first_col_zero is False.

Pass 1 checks the six cells with r >= 1 and c >= 1. Only matrix[2][3] is 0. It writes matrix[2][0] = 0 and matrix[0][3] = 0. The matrix is now [[1, 0, 3, 0], [5, 6, 7, 8], [0, 10, 11, 0]].

Pass 2 checks the same six cells. A cell becomes 0 when matrix[r][0] == 0 or matrix[0][c] == 0. matrix[1][2] = 7 has the flags 5 and 3, so it stays. The other five cells change to 0 or already are 0.

The last step clears row 0, because first_row_zero is True. Column 0 keeps its values, because first_col_zero is False. The result is [[0, 0, 0, 0], [5, 0, 7, 0], [0, 0, 0, 0]].

Why row 0 and column 0 are cleared last

After pass 1, row 0 holds the column flags and column 0 holds the row flags. Pass 2 reads them, so they must keep their flag values until pass 2 ends.

Suppose row 0 is cleared first. Every matrix[0][c] becomes 0, and pass 2 sees a flag for every column. It clears every cell. In the example above, matrix[1][2] = 7 would be lost, although it must stay.

For the same reason, pass 2 starts at r = 1 and c = 1. Row 0 and column 0 are never changed by pass 2. They are cleared in the last step, when no cell needs to read them any more.

The two booleans must be computed before pass 1. After pass 1, a 0 in row 0 can be an original value or a flag, and nothing tells them apart.

Two states in one cell: Game of Life (LC 289)

Game of Life (LC 289) has the same difficulty. Every cell must read the old state of its neighbours, but the new state is written in the same grid.

The card's idea gives the solution. Each cell holds two states, the old state in bit 0 and the new state in bit 1. While scanning, read a neighbour as board[r][c] & 1, which is its old state. Write the new state with board[r][c] |= next << 1. After the scan, board[r][c] >>= 1 makes the new state the only state.

Example: a vertical row of three live cells [[0, 1, 0], [0, 1, 0], [0, 1, 0]] becomes the horizontal row [[0, 0, 0], [1, 1, 1], [0, 0, 0]].

In both problems, the old value stays readable until every reader is done. The cost is O(M * N) time and O(1) extra space.

60-Second Pitch

"The simple solution keeps two lists of rows and columns that hold a 0. That costs O(M + N) space. I store those marks inside the matrix instead. A 0 at matrix[r][c] means row r and column c will be all 0, so I write a 0 into matrix[r][0] and matrix[0][c]. Those two cells end as 0 anyway, so the flag loses no final value. Row 0 and column 0 are also data, so I save their own answers in two booleans before I write any flag. Pass 2 zeroes the inner cells from the flags, and I clear row 0 and column 0 last. The time is O(M * N) and the extra space is O(1). The same idea of two states in one cell solves Game of Life."

Common Questions

Why can a flag be written into row 0 and column 0 without losing data?

A 0 at matrix[r][c] means row r and column c end up all 0. So matrix[r][0] and matrix[0][c] are 0 in the answer. The only information in those cells that is still needed is whether row 0 or column 0 held a 0 at the start. Two booleans store that before any flag is written.

Why do you need two booleans and not one?

One boolean cannot tell whether the 0 was in row 0 or in column 0. If the 0 was only in column 0, one shared flag would also clear row 0, which is wrong. Using matrix[0][0] as the shared flag has the same problem, because that cell belongs to both.

Why does pass 2 start at row 1 and column 1, and why is row 0 cleared last?

Row 0 and column 0 hold the flags, and pass 2 reads them. If pass 2 or an earlier step changed them, other cells would read false flags. Clearing them last means every inner cell has already read its flags.

What is the time and space complexity, and how does it compare with two lists?

The time is O(M * N), because each pass visits each cell once. The extra space is O(1), which is two booleans. The two-list solution has the same time and O(M + N) space.

How does the same idea solve Game of Life (LC 289) in place?

Each cell stores the old state in bit 0 and the new state in bit 1. Neighbours are read with & 1, so they still see the old state. The new state is written with |= next << 1. A final pass with >>= 1 keeps only the new state.

SWE Reasoning

Spot it

  • The matrix must be changed in place, and the extra memory must be O(1).
  • Each change depends on the matrix as it was, such as wiping rows and columns that hold a 0 (Set Matrix Zeroes (LC 73)).
  • Every cell must read the old state of its neighbours before its own new state is stored (Game of Life (LC 289)).
  • A cell has a known final value, so it can hold a mark first and the value later.

The invariant, step by step

  1. first_row_zero and first_col_zero are computed before anything is changed. They hold the answers of row 0 and column 0 for the original matrix.
  2. Pass 1 visits only cells with r >= 1 and c >= 1. For each 0, it writes 0 into matrix[r][0] and matrix[0][c].
  3. Those two cells end up 0 in the answer. So a flag never changes a final value.
  4. Pass 2 visits the same inner cells. A cell becomes 0 when its row flag or its column flag is 0. Row 0 and column 0 are not changed yet, so every flag is still readable.
  5. The last step clears row 0 and column 0 when the saved booleans say so.
  6. Now every row and every column that held a 0 is all 0, and no other cell changed.

Traps

  • Clearing row 0 before pass 2. Every column flag becomes 0, and pass 2 clears the whole matrix. For [[0, 1, 2], [3, 4, 5], [6, 7, 8]] the result is all zeros instead of [[0, 0, 0], [0, 4, 5], [0, 7, 8]].
  • Computing first_row_zero after pass 1. A flag written into row 0 looks like an original 0. Row 0 is then cleared although it had no 0.
  • One boolean for both. The boolean says that row 0 or column 0 had a 0, but not which one. A 0 only in column 0 would also clear row 0.
  • Zeroing the row and column during the scan. The scan meets the new zeros later and takes them for original zeros. The zeros spread to rows and columns that had none.
  • Starting pass 2 at row 0. If matrix[0][0] is 0, pass 2 sets every matrix[0][c] to 0, which makes false flags for every column.

Practice

Canonical LeetCode:Set Matrix Zeroes (LC 73)Game of Life (LC 289)Build Array from Permutation (LC 1920)Candy Crush (LC 723)
Theory Context•Math & Geometry
P2MediumMental model~5 min speedrun

In-Place Markers (First Row/Column Flags, Two States per Cell)

The problem

Set Matrix Zeroes (LC 73)(opens leetcode.com)

You get a grid of integers, matrix, with m rows and n columns. Every 0 that the grid holds at the start wipes out its whole row and its whole column: each cell in that row and in that column must end up as 0. Change matrix itself and return nothing.

Only the zeros that are there at the start count. A cell that becomes 0 because of this rule does not wipe out anything further.

A full copy of the grid (O(m * n) extra memory) or one marker per row and per column (O(m + n)) both work. LeetCode's follow-up asks for more: use only O(1) extra memory.

Example 1
Input:matrix = [[1,1,1],[1,0,1],[1,1,1]]
Output:[[1,0,1],[0,0,0],[1,0,1]]
The only 0 sits in the middle, so the middle row and the middle column become 0. The four corners keep their 1.
111101111

In-Place Marking

matrix ➔ markers in the first row / column or spare bits ➔ apply in a second pass
Spot it when: The input must be changed in place with O(1) extra memory, but each change depends on the input as it was: rows or columns to wipe, or a new state that every neighbour must still read in its old form.
In-Place Marking
Python
1def set_zeroes(matrix: list[list[int]]) -> None:
2 """Set Matrix Zeroes: row 0 and column 0 hold the flags for the other rows and columns.
3 Time: O(M * N) | Space: O(1)
4 """
5 m, n = len(matrix), len(matrix[0])
6 
7 # 1. Save Row 0's and Column 0's Own Zeros First:
8 # Pass 1 below overwrites row 0 and column 0 with flags, so whether
9 # they originally held a zero must be recorded before any flag is written.
10 first_row_zero = any(matrix[0][c] == 0 for c in range(n))
11 first_col_zero = any(matrix[r][0] == 0 for r in range(m))
12 
13 # 2. Mark Flags in Row 0 and Column 0:
14 # A zero found inside the matrix (outside row 0 and column 0) means its
15 # whole row and column end up zero, so that is recorded by zeroing the
16 # row's cell in column 0 and the column's cell in row 0.
17 for r in range(1, m):
18 for c in range(1, n):
19 if matrix[r][c] == 0:
20 matrix[r][0] = 0
21 matrix[0][c] = 0
22 
23 # 3. Apply the Flags to Every Inner Cell:
24 # Any inner cell whose row flag or column flag is set is zeroed; row 0
25 # and column 0 themselves are skipped here because they still need to be read.
26 for r in range(1, m):
27 for c in range(1, n):
28 if matrix[r][0] == 0 or matrix[0][c] == 0:
29 matrix[r][c] = 0
30 
31 # 4. Clear Row 0 and Column 0 Last:
32 # Row 0 and column 0 are cleared only after every other cell has read
33 # their flags; clearing them earlier would turn every flag true and wipe the whole matrix.
34 if first_row_zero:
35 for c in range(n):
36 matrix[0][c] = 0
37 if first_col_zero:
38 for r in range(m):
39 matrix[r][0] = 0
Invariant: Every flag written into row 0 or column 0 marks a row or column that ends up all zero anyway, and first_row_zero / first_col_zero hold row 0's and column 0's own answers from before any flag was written.

When no extra memory is allowed, store the marks in cells whose final value you already know: a zero at (r, c) means row r and column c end up all zero, so pass 1 writes the flags matrix[r][0] = 0 and matrix[0][c] = 0, and pass 2 zeroes every cell whose row flag or column flag is 0. Writing a flag loses nothing, because both flag cells become zero in the answer anyway; the same idea packs two states into one cell when every neighbour must still read it in its old form, because the old value stays readable until every reader is done, whether that means a whole cell repurposed as a flag or one bit of it holding the next state. The trap is that row 0 and column 0 are data as well as flags: compute first_row_zero and first_col_zero before pass 1 writes any flag, keep both passes to r >= 1 and c >= 1, and clear row 0 and column 0 last, because clearing them first turns every flag to 0 and wipes the whole matrix.

See it run

In-Place Marking, step by step

pass start · first_row_zero false · first_col_zero false

pass save · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 1 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass 2 · first_row_zero true · first_col_zero false

pass clear · first_row_zero true · first_col_zero false

pass clear · first_row_zero true · first_col_zero false

pass done · first_row_zero true · first_col_zero false · answer [[0,0,0,0],[5,0,7,0],[0,0,0,0]]

Each box is one cell of the matrix. Row 0 and column 0 hold the flags for the other rows and columns.

  • scan the inner cell the loop is checking
  • set 0 a cell the code writes as 0 in this step
  • flag a cell in row 0 or column 0 that holds a mark
  • keeps a cell that must stay unchanged
  • saved an original 0 in row 0 or column 0, recorded in first_row_zero or first_col_zero

Start: a 3 by 4 matrix. Row 0 and column 0 will hold the flags, so their own zeros are saved first.

first_row_zero = true and first_col_zero = false, computed before any flag is written. Pass 1 will overwrite row 0 and column 0.

matrix[1][1] is 6, not 0, so pass 1 writes no flag.

matrix[1][2] is 7, not 0, so pass 1 writes no flag.

matrix[1][3] is 8, not 0, so pass 1 writes no flag.

matrix[2][1] is 10, not 0, so pass 1 writes no flag.

matrix[2][2] is 11, not 0, so pass 1 writes no flag.

matrix[2][3] is 0, so set matrix[2][0] and matrix[0][3] to 0 as flags.

Trap: clearing row 0 now would set every matrix[0][c] to 0. Pass 2 would then also clear matrix[1][2] = 7, which must stay. So row 0 is cleared last.

matrix[0][1] is 0, so matrix[1][1] changes from 6 to 0.

matrix[1][0] is 5 and matrix[0][2] is 3, so matrix[1][2] = 7 stays.

matrix[0][3] is 0, so matrix[1][3] changes from 8 to 0.

matrix[2][0] is 0, so matrix[2][1] changes from 10 to 0.

matrix[2][0] is 0, so matrix[2][2] changes from 11 to 0.

matrix[2][0] is 0, so matrix[2][3] changes from 0 to 0.

first_row_zero is True, so row 0 is cleared now, after every other cell has read its flag.

first_col_zero is False, so column 0 keeps its values.

Done: every row and column that held a 0 is now all 0. The matrix is [[0, 0, 0, 0], [5, 0, 7, 0], [0, 0, 0, 0]].

row 0
col 01
col 10
col 23
col 34
row 1
5
6
7
8
row 2
9
10
11
0
1 / 18
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Clearing row 0 before pass 2 reads the flags

    Mistake
    if first_row_zero:
        for c in range(n):
            matrix[0][c] = 0
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:
                matrix[r][c] = 0
    Fix
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:
                matrix[r][c] = 0
    if first_row_zero:
        for c in range(n):
            matrix[0][c] = 0

    What happens: After pass 1, row 0 holds the column flags. A 0 at matrix[0][c] means "clear column c". If row 0 is cleared before pass 2, every matrix[0][c] becomes 0. Pass 2 then finds a flag in every column and clears the whole matrix.

    Example: matrix = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
    Mistake: returns [[0, 0, 0], [0, 0, 0], [0, 0, 0]]Fix: returns [[0, 0, 0], [0, 4, 5], [0, 7, 8]]
  2. 2

    Gotcha 2: Using one flag for both row 0 and column 0

    Mistake
    first_zero = any(matrix[0][c] == 0 for c in range(n)) or any(matrix[r][0] == 0 for r in range(m))
    first_row_zero = first_col_zero = first_zero
    Fix
    first_row_zero = any(matrix[0][c] == 0 for c in range(n))
    first_col_zero = any(matrix[r][0] == 0 for r in range(m))

    What happens: One shared flag can only say that row 0 or column 0 had a 0. It can't say which one. So a 0 only in column 0 also clears all of row 0. Using matrix[0][0] as that one flag has the same problem, because the cell is in both. The blueprint keeps two booleans, first_row_zero and first_col_zero.

    Example: matrix = [[1, 1], [0, 1]]
    Mistake: returns [[0, 0], [0, 0]]Fix: returns [[0, 1], [0, 0]]
  3. 3

    Gotcha 3: Computing first_row_zero after pass 1 writes the flags

    Mistake
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][c] == 0:
                matrix[r][0] = 0
                matrix[0][c] = 0
    first_row_zero = any(matrix[0][c] == 0 for c in range(n))
    Fix
    first_row_zero = any(matrix[0][c] == 0 for c in range(n))
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][c] == 0:
                matrix[r][0] = 0
                matrix[0][c] = 0

    What happens: Pass 1 writes 0s into row 0 as column flags. After that, any(matrix[0][c] == 0 ...) can't tell a flag from a 0 that was in the input. It finds a flag and sets first_row_zero to True. The last step then clears all of row 0.

    Example: matrix = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]
    Mistake: returns [[0, 0, 0], [0, 0, 0], [1, 0, 1]]Fix: returns [[1, 0, 1], [0, 0, 0], [1, 0, 1]]
  4. 4

    Gotcha 4: Zeroing row r and column c during the scan

    Mistake
    if matrix[r][c] == 0:
        for k in range(n):
            matrix[r][k] = 0
        for k in range(m):
            matrix[k][c] = 0
    Fix
    if matrix[r][c] == 0:
        matrix[r][0] = 0
        matrix[0][c] = 0

    What happens: Pass 1 scans the cells in order. If it writes 0s into the row and the column at once, the scan meets those new 0s later. It takes them for 0s that were in the input. Each one clears another row and column. The 0s spread to rows and columns that had none.

    Example: matrix = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]
    Mistake: returns [[1, 0, 0], [0, 0, 0], [0, 0, 0]]Fix: returns [[1, 0, 1], [0, 0, 0], [1, 0, 1]]
  5. 5

    Gotcha 5: Starting pass 2 at row 0 instead of row 1

    Mistake
    for r in range(m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:
    Fix
    for r in range(1, m):
        for c in range(1, n):
            if matrix[r][0] == 0 or matrix[0][c] == 0:

    What happens: Row 0 holds the column flags until pass 2 ends. If pass 2 starts at row 0 and matrix[0][0] == 0, it sets every matrix[0][c] to 0. Every later row then finds a flag in every column and is cleared.

    Example: matrix = [[0, 1], [1, 1]]
    Mistake: returns [[0, 0], [0, 0]]Fix: returns [[0, 0], [0, 1]]
Not this one if…
  • The values are 1..n and each belongs in its own slot, as in First Missing Positive (LC 41): once a value is home, its old slot content is never needed again, so no mark has to keep the old value readable. See Cyclic Sort

Deep Dive

Why a flag in row 0 or column 0 loses nothing

Set Matrix Zeroes (LC 73) needs to remember which rows and columns contain a 0. The simple way stores two lists, one for the rows and one for the columns, which costs O(M + N) extra space. The card stores the same information inside the matrix, so the extra space is O(1).

A flag must be written in a cell whose final value is already known. If matrix[r][c] is 0, then row r and column c end up all 0. So matrix[r][0] and matrix[0][c] end up 0 in the answer. Writing a 0 there as a flag changes no final value.

The flag still destroys the old value of that cell. This matters only for the zeros that were already in row 0 and column 0. A 0 at matrix[r][0] with r >= 1 already acts as the flag for row r. It also means that column 0 must be cleared, and one boolean records that: first_col_zero. A 0 in row 0 is handled in the same way by first_row_zero. That is why both booleans are computed before pass 1.

The two passes, traced on a 3 by 4 matrix

Take matrix = [[1, 0, 3, 4], [5, 6, 7, 8], [9, 10, 11, 0]]. Row 0 holds a 0 at matrix[0][1], so first_row_zero is True. Column 0 holds no 0, so first_col_zero is False.

Pass 1 checks the six cells with r >= 1 and c >= 1. Only matrix[2][3] is 0. It writes matrix[2][0] = 0 and matrix[0][3] = 0. The matrix is now [[1, 0, 3, 0], [5, 6, 7, 8], [0, 10, 11, 0]].

Pass 2 checks the same six cells. A cell becomes 0 when matrix[r][0] == 0 or matrix[0][c] == 0. matrix[1][2] = 7 has the flags 5 and 3, so it stays. The other five cells change to 0 or already are 0.

The last step clears row 0, because first_row_zero is True. Column 0 keeps its values, because first_col_zero is False. The result is [[0, 0, 0, 0], [5, 0, 7, 0], [0, 0, 0, 0]].

Why row 0 and column 0 are cleared last

After pass 1, row 0 holds the column flags and column 0 holds the row flags. Pass 2 reads them, so they must keep their flag values until pass 2 ends.

Suppose row 0 is cleared first. Every matrix[0][c] becomes 0, and pass 2 sees a flag for every column. It clears every cell. In the example above, matrix[1][2] = 7 would be lost, although it must stay.

For the same reason, pass 2 starts at r = 1 and c = 1. Row 0 and column 0 are never changed by pass 2. They are cleared in the last step, when no cell needs to read them any more.

The two booleans must be computed before pass 1. After pass 1, a 0 in row 0 can be an original value or a flag, and nothing tells them apart.

Two states in one cell: Game of Life (LC 289)

Game of Life (LC 289) has the same difficulty. Every cell must read the old state of its neighbours, but the new state is written in the same grid.

The card's idea gives the solution. Each cell holds two states, the old state in bit 0 and the new state in bit 1. While scanning, read a neighbour as board[r][c] & 1, which is its old state. Write the new state with board[r][c] |= next << 1. After the scan, board[r][c] >>= 1 makes the new state the only state.

Example: a vertical row of three live cells [[0, 1, 0], [0, 1, 0], [0, 1, 0]] becomes the horizontal row [[0, 0, 0], [1, 1, 1], [0, 0, 0]].

In both problems, the old value stays readable until every reader is done. The cost is O(M * N) time and O(1) extra space.

60-Second Pitch

"The simple solution keeps two lists of rows and columns that hold a 0. That costs O(M + N) space. I store those marks inside the matrix instead. A 0 at matrix[r][c] means row r and column c will be all 0, so I write a 0 into matrix[r][0] and matrix[0][c]. Those two cells end as 0 anyway, so the flag loses no final value. Row 0 and column 0 are also data, so I save their own answers in two booleans before I write any flag. Pass 2 zeroes the inner cells from the flags, and I clear row 0 and column 0 last. The time is O(M * N) and the extra space is O(1). The same idea of two states in one cell solves Game of Life."

Common Questions

Why can a flag be written into row 0 and column 0 without losing data?

A 0 at matrix[r][c] means row r and column c end up all 0. So matrix[r][0] and matrix[0][c] are 0 in the answer. The only information in those cells that is still needed is whether row 0 or column 0 held a 0 at the start. Two booleans store that before any flag is written.

Why do you need two booleans and not one?

One boolean cannot tell whether the 0 was in row 0 or in column 0. If the 0 was only in column 0, one shared flag would also clear row 0, which is wrong. Using matrix[0][0] as the shared flag has the same problem, because that cell belongs to both.

Why does pass 2 start at row 1 and column 1, and why is row 0 cleared last?

Row 0 and column 0 hold the flags, and pass 2 reads them. If pass 2 or an earlier step changed them, other cells would read false flags. Clearing them last means every inner cell has already read its flags.

What is the time and space complexity, and how does it compare with two lists?

The time is O(M * N), because each pass visits each cell once. The extra space is O(1), which is two booleans. The two-list solution has the same time and O(M + N) space.

How does the same idea solve Game of Life (LC 289) in place?

Each cell stores the old state in bit 0 and the new state in bit 1. Neighbours are read with & 1, so they still see the old state. The new state is written with |= next << 1. A final pass with >>= 1 keeps only the new state.

SWE Reasoning

Spot it

  • The matrix must be changed in place, and the extra memory must be O(1).
  • Each change depends on the matrix as it was, such as wiping rows and columns that hold a 0 (Set Matrix Zeroes (LC 73)).
  • Every cell must read the old state of its neighbours before its own new state is stored (Game of Life (LC 289)).
  • A cell has a known final value, so it can hold a mark first and the value later.

The invariant, step by step

  1. first_row_zero and first_col_zero are computed before anything is changed. They hold the answers of row 0 and column 0 for the original matrix.
  2. Pass 1 visits only cells with r >= 1 and c >= 1. For each 0, it writes 0 into matrix[r][0] and matrix[0][c].
  3. Those two cells end up 0 in the answer. So a flag never changes a final value.
  4. Pass 2 visits the same inner cells. A cell becomes 0 when its row flag or its column flag is 0. Row 0 and column 0 are not changed yet, so every flag is still readable.
  5. The last step clears row 0 and column 0 when the saved booleans say so.
  6. Now every row and every column that held a 0 is all 0, and no other cell changed.

Traps

  • Clearing row 0 before pass 2. Every column flag becomes 0, and pass 2 clears the whole matrix. For [[0, 1, 2], [3, 4, 5], [6, 7, 8]] the result is all zeros instead of [[0, 0, 0], [0, 4, 5], [0, 7, 8]].
  • Computing first_row_zero after pass 1. A flag written into row 0 looks like an original 0. Row 0 is then cleared although it had no 0.
  • One boolean for both. The boolean says that row 0 or column 0 had a 0, but not which one. A 0 only in column 0 would also clear row 0.
  • Zeroing the row and column during the scan. The scan meets the new zeros later and takes them for original zeros. The zeros spread to rows and columns that had none.
  • Starting pass 2 at row 0. If matrix[0][0] is 0, pass 2 sets every matrix[0][c] to 0, which makes false flags for every column.

Practice

Canonical LeetCode:Set Matrix Zeroes (LC 73)Game of Life (LC 289)Build Array from Permutation (LC 1920)Candy Crush (LC 723)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•