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•Backtracking & State Search

Builds on Permutations, which you haven’t finished yet.

P1HardBuilt on Subsets~6 min speedrun

Constraint Satisfaction Backtracking (N-Queens, Sudoku)

Built on
PermutationsEach placement is checked against marked sets (columns and diagonals), and the marks are removed when the search backs out.
Grows into
Find One Solution
The problem

N-Queens (LC 51)(opens leetcode.com)

Placing n chess queens on an n x n board so that no two queens share a row, column, or diagonal is the classic n-queens puzzle.

Given n, return every distinct board arrangement that solves the puzzle, in any order.

Represent each solution as a list of row strings, where 'Q' marks a queen and '.' marks an empty square.

Example 1
Input:n = 4
Output:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
A 4x4 board has exactly two safe queen arrangements: one places queens at columns 1,3,0,2 (row by row), and the other at columns 2,0,3,1 -- both leave no queen sharing a row, column, or diagonal with another.
.Q.....QQ.....Q.

Constraint Backtracking

board coordinates ➔ validation arrays (rows, cols, diags) ➔ explore & un-mark
Spot it when: You fill positions (a board or a string) one at a time, checking each placement against the rules before going deeper, or walk paths on a board where a cell can't be reused within one path: N-Queens, Sudoku, balanced parentheses, word search, collecting gold.
Constraint Backtracking
Python
1from typing import Callable, Iterable, List, Set, TypeVar
2 
3Item = TypeVar("Item")
4 
5 
6def place_and_check(slots: int, options: Callable[[int], Iterable[Item]], is_valid: Callable[[int, Item], bool], place: Callable[[int, Item], None], unplace: Callable[[int, Item], None], record: Callable[[], None]) -> None:
7 """Constraint Backtracking: fill slots 0..slots-1 one option each, checking every option as it is placed.
8 is_valid(slot, option) reads the marks of the options already placed; place and unplace set and clear them.
9 record() is called once for every complete filling.
10 B = the calls to backtrack. I = the loop steps over all calls. S = the complete fillings recorded.
11 T = the cost of one options, is_valid, place, unplace or record call.
12 Time: O((B + I + S) * T) | Space: O(slots) recursion stack, plus the marks
13 """
14 def backtrack(slot: int) -> None:
15 # 1. Record a Complete Filling:
16 # Every option already placed passed is_valid when it was placed, so once
17 # slot reaches slots, every slot is filled and every rule still holds.
18 if slot == slots:
19 record()
20 return
21 
22 for option in options(slot):
23 # 2. Check Before Placing:
24 # is_valid reads the marks of the options already placed; an option that
25 # breaks a rule is cut off here, before it is ever placed on the board.
26 if not is_valid(slot, option):
27 continue
28 
29 # 3. Choose, Explore, Un-choose:
30 # place marks option for slot and recurse to the next slot; unplace then
31 # clears every mark place set, so a later option can reuse the same marks.
32 place(slot, option)
33 backtrack(slot + 1)
34 unplace(slot, option)
35 
36 backtrack(0)
Invariant: Rows 0 to r - 1 each hold one queen, no two of them attack each other, and cols, diag and anti hold exactly their columns and diagonals.

Place one queen per row, so backtrack(r) only chooses a column c for row r, and skip c when c, r - c or r + c is already in cols, diag or anti. Each queen is checked the moment it is placed, so a board that breaks a rule is cut off at once instead of being built and tested at the end, and one queen per row already rules out attacks along a row. The trap is the undo: after backtrack(r + 1), reset board[r][c] and remove c, r - c and r + c from the three sets, or a queen that was taken back still blocks later rows.

See it run

Constraint Backtracking on a 4 x 4 board, step by step

r 0 · cols {} · diag {} · anti {}

r 0 · cols {0} · diag {0} · anti {0}

r 1 · cols {0, 2} · diag {-1, 0} · anti {0, 3}

r 2 · cols {0, 2} · diag {-1, 0} · anti {0, 3}

r 1 · cols {0} · diag {0} · anti {0}

r 1 · cols {0, 3} · diag {-2, 0} · anti {0, 4}

r 2 · cols {0, 1, 3} · diag {-2, 0, 1} · anti {0, 3, 4}

r 3 · cols {0, 1, 3} · diag {-2, 0, 1} · anti {0, 3, 4}

r 2 · cols {0, 3} · diag {-2, 0} · anti {0, 4}

r 1 · cols {0} · diag {0} · anti {0}

r 0 · cols {} · diag {} · anti {}

r 0 · cols {1} · diag {-1} · anti {1}

r 1 · cols {1, 3} · diag {-2, -1} · anti {1, 4}

r 2 · cols {0, 1, 3} · diag {-2, -1, 2} · anti {1, 2, 4}

r 3 · cols {0, 1, 2, 3} · diag {-2, -1, 1, 2} · anti {1, 2, 4, 5}

r 4 · cols {0, 1, 2, 3} · diag {-2, -1, 1, 2} · anti {1, 2, 4, 5}

r - · cols {0, 1, 2, 3} · diag {-2, -1, 1, 2} · anti {1, 2, 4, 5} · first board 1,3,0,2

Each row of the table is one board row and each column is one board column. Q is a queen and . is an empty cell. The replay stops after the first board is recorded.

  • place the cell where the queen is placed now
  • queen a queen placed earlier and still on the board
  • attacked a cell in this row that cols, diag or anti rules out
  • undo a cell that was just reset to empty

Start: the 4 x 4 board is empty, cols, diag and anti are empty, and backtrack(0) begins.

Column 0 is the first free column, so place board[0][0] and add 0, 0 and 0 to cols, diag and anti.

In row 1, columns 0 and 1 are attacked. Column 2 is the first free column, so place board[1][2] and add 2, -1 and 3 to cols, diag and anti.

In row 2, columns 0, 1, 2 and 3 are all attacked, so no queen fits. The call returns and the previous queen is undone.

Undo board[1][2]: set it back to "." and remove 2, -1 and 3 from cols, diag and anti.

The loop goes on and column 3 is free, so place board[1][3] and add 3, -2 and 4 to cols, diag and anti.

In row 2, column 0 is attacked. Column 1 is the first free column, so place board[2][1] and add 1, 1 and 3 to cols, diag and anti.

In row 3, columns 0, 1, 2 and 3 are all attacked, so no queen fits. The call returns and the previous queen is undone.

Undo board[2][1]: set it back to "." and remove 1, 1 and 3 from cols, diag and anti.

Undo board[1][3]: set it back to "." and remove 3, -2 and 4 from cols, diag and anti.

Undo board[0][0]: set it back to "." and remove 0, 0 and 0 from cols, diag and anti.

The loop goes on and column 1 is free, so place board[0][1] and add 1, -1 and 1 to cols, diag and anti.

In row 1, columns 0, 1 and 2 are attacked. Column 3 is the first free column, so place board[1][3] and add 3, -2 and 4 to cols, diag and anti.

Column 0 is the first free column, so place board[2][0] and add 0, 2 and 2 to cols, diag and anti.

In row 3, columns 0 and 1 are attacked. Column 2 is the first free column, so place board[3][2] and add 2, 1 and 5 to cols, diag and anti.

r = 4 equals n, so every row has a queen. The board ".Q..", "...Q", "Q...", "..Q." is recorded.

Done: the first board has queens in columns 1,3,0,2, one for each row. The search would now undo these queens and go on to find the other boards.

row 0
col 0.
col 1.
col 2.
col 3.
row 1
.
.
.
.
row 2
.
.
.
.
row 3
.
.
.
.
1 / 17
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Taking a queen back without removing its marks

    Mistake
    board[r][c] = "."
    Fix
    board[r][c] = "."
    cols.remove(c)
    diag.remove(r - c)
    anti.remove(r + c)

    What happens: Placing a queen adds c, r - c and r + c to the three sets. If the undo only resets board[r][c], those numbers stay in the sets. A queen that is gone then still blocks its column and diagonals in later rows. So no board is ever completed.

    Example: n = 4 (the number of boards)
    Mistake: returns 0Fix: returns 2
  2. 2

    Gotcha 2: Checking only one of the two diagonals

    Mistake
    if c in cols or r - c in diag:
    Fix
    if c in cols or r - c in diag or r + c in anti:

    What happens: Along a down-right diagonal, r - c stays the same. Along a down-left diagonal, r + c stays the same. Checking only diag lets two queens share a down-left diagonal. Both queens add the same r + c to anti, and the set keeps it once. The second anti.remove(r + c) then raises a KeyError.

    Example: n = 4 (the number of boards)
    Mistake: raises KeyErrorFix: returns 2
  3. 3

    Gotcha 3: Saving board itself instead of a copy

    Mistake
    results.append(board)
    Fix
    results.append(["".join(row) for row in board])

    What happens: board is one list that the search keeps changing. results.append(board) stores that same list. The undo steps later set every queen back to ".", so the saved board ends up empty.

    Example: n = 1
    Mistake: returns [[['.']]]Fix: returns [['Q']]
  4. 4

    Gotcha 4: Sudoku Solver (LC 37): not returning True when full

    Mistake
    backtrack()
    Fix
    if backtrack():
        return True

    What happens: Sudoku Solver (LC 37) needs one solved board, not every board. When the last cell is filled, backtrack() returns True. If the caller ignores that value, it runs the undo step next. The undo sets the cell back to ".", so the solution is lost.

    Example: A solved board with the cell in row 0, column 0 made empty (the value of that cell)
    Mistake: returns '.'Fix: returns '1'
  5. 5

    Gotcha 5: Not restoring a grid cell after the search leaves it

    Mistake
    board[r][c] = "#"
    found = any(dfs(r + dr, c + dc, k + 1) for dr, dc in steps)
    return found
    Fix
    board[r][c] = "#"
    found = any(dfs(r + dr, c + dc, k + 1) for dr, dc in steps)
    board[r][c] = word[k]
    return found

    What happens: In Word Search (LC 79), the mark is the cell itself. The search sets board[r][c] = "#" before it goes to the neighbours. If it does not put the letter back, a later path finds that cell blocked. So a word that is on the board is not found.

    Example: Word Search (LC 79): board = [["A", "B", "B"]], word = "BBA"
    Mistake: returns FalseFix: returns True
Not this one if…
  • Each cell only has to be reached once in total, as in Flood Fill (LC 733), so nothing is undone when the search steps back. See Flood Fill

Deep Dive

Check before placing: one queen per row, three sets for the rest

Permutations already shows the used marks: a choice is marked, the call recurses, and the choice is unmarked. This card adds one rule. Each option is tested against the marks of the options already placed, before it is placed. An option that breaks a rule is skipped, and its whole branch is never built.

In N-Queens (LC 51), the slot is a row. backtrack(r) chooses a column c for row r. One queen per row already rules out two queens in a row, so only columns and diagonals need a test.

A column is identified by c. A down-right diagonal is identified by r - c, because both r and c grow by 1 along it. A down-left diagonal is identified by r + c, because r grows by 1 and c shrinks by 1. So cols, diag and anti are three sets of numbers, and each test is O(1).

Example: a queen at (0, 0) has r - c = 0, and so does (1, 1). A queen at (0, 3) has r + c = 3, and so do (1, 2), (2, 1) and (3, 0). For n = 4, there are 24 ways to put one queen in each row and column. The search places only 16 queens in total and finds 2 boards.

One trace: dead rows and the undo, for n = 4

The board replays solveNQueens(4) until the first board is recorded. The answer for n = 4 is 2 boards. The first one is [".Q..","...Q","Q...","..Q."], and it has queens in columns 1, 3, 0, 2.

The search places (0, 0). In row 1, columns 0 and 1 are attacked, so (1, 2) is placed. In row 2, all four columns are attacked: columns 0 and 2 by cols, column 1 by anti, and column 3 by diag. The call returns, and the undo of (1, 2) removes 2, -1 and 3 from the three sets. This is the step marked as the trap on the board.

Row 1 then takes (1, 3), and row 2 takes (2, 1). In row 3, all four columns are attacked, so the search undoes (2, 1), then (1, 3), then (0, 0). Row 0 moves on to (0, 1). Rows 1, 2 and 3 take columns 3, 0 and 2, r reaches 4, and the first board is recorded.

That is 8 placed queens, 4 undos and 2 dead rows before the first board. The full search for n = 4 places 16 queens in 17 calls of backtrack. It finds the second board ["..Q.","Q...","...Q",".Q.."] after the first.

The undo must reverse every mark that the placement made

A placement writes four things: board[r][c], cols, diag and anti. The undo must write all four back, in any order. Then the state after backtrack(r + 1) is exactly the state before place, so the next column in the loop sees the marks of the queens above row r and nothing else.

A mark that stays is a queen that no longer exists but still blocks. For n = 4, an undo that resets only board[r][c] finds 0 boards instead of 2.

The sets use remove, not discard. If the code is correct, each undo removes a number the matching placement added. A KeyError is an early sign of a bug, such as a test that checks only diag. Then two queens share a down-left diagonal, anti holds the number once, and the second remove fails.

A saved board must be a copy. results.append(["".join(row) for row in board]) builds new strings. Saving board itself stores the list that the undo steps later empty.

Example: for n = 1, the board is [["Q"]] at record time. With results.append(board), the result is [[['.']]] after the undo.

The cost: placements times the cost of one test, and what changes for other puzzles

The card's complexity line is O((B + I + S) * T). B is the number of backtrack calls, I is the number of loop steps, S is the number of complete boards, and T is the cost of one option test, placement or undo. In N-Queens, each of them is O(1) except the copy of a board, so the search time is bounded by O(N!).

The bound is loose. Row r has at most N - r free columns, which gives N! at most. The tests cut far more. For n = 8, 2,056 queens are placed in 2,057 calls, and 92 boards are found. There are 8! = 40,320 ways to put one queen in each row and column.

The board takes O(N^2) space. The three sets and the recursion stack take O(N), and the saved boards are not counted.

Other puzzles use the same loop. N-Queens II (LC 52) asks only for the number of boards, so record adds 1 and no board is copied. Sudoku Solver (LC 37) needs one solved board, so backtrack returns True when the board is full, and the caller must return at once without undoing. Word Search (LC 79) marks a cell before it goes deeper and clears the mark after, so one path cannot reuse a cell.

Example: the same template with the slot as a position in a string and the options ( and ) gives Generate Parentheses (LC 22). The test is that a ) needs an open ( before it.

60-Second Pitch

"I put one queen in each row, so backtrack(r) chooses only a column c for row r. A column is c, a down-right diagonal is r - c, and a down-left diagonal is r + c. I keep each in a set, so a test is O(1). Before I place a queen, I test c, r - c and r + c. If one is taken, I skip that column, and the branch is never built. When r reaches n, I save a copy of the board. After the recursive call, I reset the cell and remove all three numbers. That keeps the marks exact for the next column. The time is bounded by O(N!), and in practice far less. For n = 8, the search places 2,056 queens and finds 92 boards. The space is O(N^2) for the board and O(N) for the sets and the stack."

Common Questions

Why do r - c and r + c identify the diagonals?

Moving down-right adds 1 to both r and c, so r - c does not change. Moving down-left adds 1 to r and subtracts 1 from c, so r + c does not change. Two cells are on the same diagonal exactly when they have the same value.

Why does the code test an option before it places it?

A bad option is then cut off at once, and no deeper call is made for it. If the code tested only complete boards, it would build every filling first. For n = 8, the early test places 2,056 queens, and one queen per row with no early test gives 8^8 = 16,777,216 fillings to check.

How do you change this to count boards, as in N-Queens II (LC 52)?

Replace the copy of the board with count += 1. The three sets already carry all the information needed for the test, so board is not needed at all. This saves the O(N^2) copy per board.

What changes for Sudoku Solver (LC 37)?

The slot is the next empty cell, the options are the digits 1 to 9, and the marks are a set for each row, column and box. The question asks for one solution, so backtrack returns True when the board is full, and every caller returns True at once without undoing.

How can the three sets be made faster?

Store each set as the bits of one integer, so a test is one bitwise operation, and a placement sets three bits. The search still places the same queens. Only the constant factor drops. Say that in an interview after the sets version works.

SWE Reasoning

Spot it

  • A board or a string is filled one position at a time, and each position takes one value from a small set.
  • A rule links a new value to the values already placed: no two queens attack, no repeated digit in a row, no ) without an open (.
  • A rule can be tested the moment a value is placed, so a bad value is known at once.
  • The question asks for every valid filling, the number of them, or one valid filling.
  • On a board of cells, a path must not use the same cell twice, as in Word Search (LC 79).

The invariant, step by step

  1. backtrack(r) is called when rows 0 to r - 1 each hold one queen, and no two of them attack each other.
  2. cols, diag and anti hold exactly the columns and the two kinds of diagonals of those queens.
  3. If r == n, all n rows have a queen and every queen passed the test when it was placed. The call records a copy of the board.
  4. Otherwise the loop tries each column c. If c, r - c or r + c is in its set, a placed queen attacks the cell, and the loop continues with the next column.
  5. If the cell is free, the code sets board[r][c] and adds c, r - c and r + c to the sets. The invariant now holds for r + 1, and backtrack(r + 1) runs.
  6. After the call, the code resets board[r][c] and removes the three numbers. The state is again as it was before the placement, so the next column starts from the same marks.

Traps

  • An undo that skips the sets. A queen that was taken back still blocks its column and diagonals. For n = 4, the result is 0 boards instead of 2.
  • A test of only one diagonal. Two queens can share a down-left diagonal. The set anti holds the number once, and the second remove raises KeyError.
  • Saving board itself. The undo steps empty the saved board. Save a copy of the rows as strings.
  • Testing after the whole board is built. The search then builds every filling before it rejects one. Test each option before place.
  • Ignoring the return value in Sudoku Solver (LC 37). The full board returns True. If the caller does not stop, the undo clears the last cell and the solution is lost.

Practice

Canonical LeetCode:N-Queens (LC 51)Sudoku Solver (LC 37)Unique Paths III (LC 980)Path with Maximum Gold (LC 1219)Word Search II (LC 212)Android Unlock Patterns (LC 351)Generate Parentheses (LC 22)Word Search (LC 79)Matchsticks to Square (LC 473)The k-th Lexicographical String of All Happy Strings of Length n (LC 1415)N-Queens II (LC 52)
Theory Context•Backtracking & State Search

Builds on Permutations, which you haven’t finished yet.

P1HardBuilt on Subsets~6 min speedrun

Constraint Satisfaction Backtracking (N-Queens, Sudoku)

Built on
PermutationsEach placement is checked against marked sets (columns and diagonals), and the marks are removed when the search backs out.
Grows into
Find One Solution
The problem

N-Queens (LC 51)(opens leetcode.com)

Placing n chess queens on an n x n board so that no two queens share a row, column, or diagonal is the classic n-queens puzzle.

Given n, return every distinct board arrangement that solves the puzzle, in any order.

Represent each solution as a list of row strings, where 'Q' marks a queen and '.' marks an empty square.

Example 1
Input:n = 4
Output:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
A 4x4 board has exactly two safe queen arrangements: one places queens at columns 1,3,0,2 (row by row), and the other at columns 2,0,3,1 -- both leave no queen sharing a row, column, or diagonal with another.
.Q.....QQ.....Q.

Constraint Backtracking

board coordinates ➔ validation arrays (rows, cols, diags) ➔ explore & un-mark
Spot it when: You fill positions (a board or a string) one at a time, checking each placement against the rules before going deeper, or walk paths on a board where a cell can't be reused within one path: N-Queens, Sudoku, balanced parentheses, word search, collecting gold.
Constraint Backtracking
Python
1from typing import Callable, Iterable, List, Set, TypeVar
2 
3Item = TypeVar("Item")
4 
5 
6def place_and_check(slots: int, options: Callable[[int], Iterable[Item]], is_valid: Callable[[int, Item], bool], place: Callable[[int, Item], None], unplace: Callable[[int, Item], None], record: Callable[[], None]) -> None:
7 """Constraint Backtracking: fill slots 0..slots-1 one option each, checking every option as it is placed.
8 is_valid(slot, option) reads the marks of the options already placed; place and unplace set and clear them.
9 record() is called once for every complete filling.
10 B = the calls to backtrack. I = the loop steps over all calls. S = the complete fillings recorded.
11 T = the cost of one options, is_valid, place, unplace or record call.
12 Time: O((B + I + S) * T) | Space: O(slots) recursion stack, plus the marks
13 """
14 def backtrack(slot: int) -> None:
15 # 1. Record a Complete Filling:
16 # Every option already placed passed is_valid when it was placed, so once
17 # slot reaches slots, every slot is filled and every rule still holds.
18 if slot == slots:
19 record()
20 return
21 
22 for option in options(slot):
23 # 2. Check Before Placing:
24 # is_valid reads the marks of the options already placed; an option that
25 # breaks a rule is cut off here, before it is ever placed on the board.
26 if not is_valid(slot, option):
27 continue
28 
29 # 3. Choose, Explore, Un-choose:
30 # place marks option for slot and recurse to the next slot; unplace then
31 # clears every mark place set, so a later option can reuse the same marks.
32 place(slot, option)
33 backtrack(slot + 1)
34 unplace(slot, option)
35 
36 backtrack(0)
Invariant: Rows 0 to r - 1 each hold one queen, no two of them attack each other, and cols, diag and anti hold exactly their columns and diagonals.

Place one queen per row, so backtrack(r) only chooses a column c for row r, and skip c when c, r - c or r + c is already in cols, diag or anti. Each queen is checked the moment it is placed, so a board that breaks a rule is cut off at once instead of being built and tested at the end, and one queen per row already rules out attacks along a row. The trap is the undo: after backtrack(r + 1), reset board[r][c] and remove c, r - c and r + c from the three sets, or a queen that was taken back still blocks later rows.

See it run

Constraint Backtracking on a 4 x 4 board, step by step

r 0 · cols {} · diag {} · anti {}

r 0 · cols {0} · diag {0} · anti {0}

r 1 · cols {0, 2} · diag {-1, 0} · anti {0, 3}

r 2 · cols {0, 2} · diag {-1, 0} · anti {0, 3}

r 1 · cols {0} · diag {0} · anti {0}

r 1 · cols {0, 3} · diag {-2, 0} · anti {0, 4}

r 2 · cols {0, 1, 3} · diag {-2, 0, 1} · anti {0, 3, 4}

r 3 · cols {0, 1, 3} · diag {-2, 0, 1} · anti {0, 3, 4}

r 2 · cols {0, 3} · diag {-2, 0} · anti {0, 4}

r 1 · cols {0} · diag {0} · anti {0}

r 0 · cols {} · diag {} · anti {}

r 0 · cols {1} · diag {-1} · anti {1}

r 1 · cols {1, 3} · diag {-2, -1} · anti {1, 4}

r 2 · cols {0, 1, 3} · diag {-2, -1, 2} · anti {1, 2, 4}

r 3 · cols {0, 1, 2, 3} · diag {-2, -1, 1, 2} · anti {1, 2, 4, 5}

r 4 · cols {0, 1, 2, 3} · diag {-2, -1, 1, 2} · anti {1, 2, 4, 5}

r - · cols {0, 1, 2, 3} · diag {-2, -1, 1, 2} · anti {1, 2, 4, 5} · first board 1,3,0,2

Each row of the table is one board row and each column is one board column. Q is a queen and . is an empty cell. The replay stops after the first board is recorded.

  • place the cell where the queen is placed now
  • queen a queen placed earlier and still on the board
  • attacked a cell in this row that cols, diag or anti rules out
  • undo a cell that was just reset to empty

Start: the 4 x 4 board is empty, cols, diag and anti are empty, and backtrack(0) begins.

Column 0 is the first free column, so place board[0][0] and add 0, 0 and 0 to cols, diag and anti.

In row 1, columns 0 and 1 are attacked. Column 2 is the first free column, so place board[1][2] and add 2, -1 and 3 to cols, diag and anti.

In row 2, columns 0, 1, 2 and 3 are all attacked, so no queen fits. The call returns and the previous queen is undone.

Undo board[1][2]: set it back to "." and remove 2, -1 and 3 from cols, diag and anti.

The loop goes on and column 3 is free, so place board[1][3] and add 3, -2 and 4 to cols, diag and anti.

In row 2, column 0 is attacked. Column 1 is the first free column, so place board[2][1] and add 1, 1 and 3 to cols, diag and anti.

In row 3, columns 0, 1, 2 and 3 are all attacked, so no queen fits. The call returns and the previous queen is undone.

Undo board[2][1]: set it back to "." and remove 1, 1 and 3 from cols, diag and anti.

Undo board[1][3]: set it back to "." and remove 3, -2 and 4 from cols, diag and anti.

Undo board[0][0]: set it back to "." and remove 0, 0 and 0 from cols, diag and anti.

The loop goes on and column 1 is free, so place board[0][1] and add 1, -1 and 1 to cols, diag and anti.

In row 1, columns 0, 1 and 2 are attacked. Column 3 is the first free column, so place board[1][3] and add 3, -2 and 4 to cols, diag and anti.

Column 0 is the first free column, so place board[2][0] and add 0, 2 and 2 to cols, diag and anti.

In row 3, columns 0 and 1 are attacked. Column 2 is the first free column, so place board[3][2] and add 2, 1 and 5 to cols, diag and anti.

r = 4 equals n, so every row has a queen. The board ".Q..", "...Q", "Q...", "..Q." is recorded.

Done: the first board has queens in columns 1,3,0,2, one for each row. The search would now undo these queens and go on to find the other boards.

row 0
col 0.
col 1.
col 2.
col 3.
row 1
.
.
.
.
row 2
.
.
.
.
row 3
.
.
.
.
1 / 17
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Taking a queen back without removing its marks

    Mistake
    board[r][c] = "."
    Fix
    board[r][c] = "."
    cols.remove(c)
    diag.remove(r - c)
    anti.remove(r + c)

    What happens: Placing a queen adds c, r - c and r + c to the three sets. If the undo only resets board[r][c], those numbers stay in the sets. A queen that is gone then still blocks its column and diagonals in later rows. So no board is ever completed.

    Example: n = 4 (the number of boards)
    Mistake: returns 0Fix: returns 2
  2. 2

    Gotcha 2: Checking only one of the two diagonals

    Mistake
    if c in cols or r - c in diag:
    Fix
    if c in cols or r - c in diag or r + c in anti:

    What happens: Along a down-right diagonal, r - c stays the same. Along a down-left diagonal, r + c stays the same. Checking only diag lets two queens share a down-left diagonal. Both queens add the same r + c to anti, and the set keeps it once. The second anti.remove(r + c) then raises a KeyError.

    Example: n = 4 (the number of boards)
    Mistake: raises KeyErrorFix: returns 2
  3. 3

    Gotcha 3: Saving board itself instead of a copy

    Mistake
    results.append(board)
    Fix
    results.append(["".join(row) for row in board])

    What happens: board is one list that the search keeps changing. results.append(board) stores that same list. The undo steps later set every queen back to ".", so the saved board ends up empty.

    Example: n = 1
    Mistake: returns [[['.']]]Fix: returns [['Q']]
  4. 4

    Gotcha 4: Sudoku Solver (LC 37): not returning True when full

    Mistake
    backtrack()
    Fix
    if backtrack():
        return True

    What happens: Sudoku Solver (LC 37) needs one solved board, not every board. When the last cell is filled, backtrack() returns True. If the caller ignores that value, it runs the undo step next. The undo sets the cell back to ".", so the solution is lost.

    Example: A solved board with the cell in row 0, column 0 made empty (the value of that cell)
    Mistake: returns '.'Fix: returns '1'
  5. 5

    Gotcha 5: Not restoring a grid cell after the search leaves it

    Mistake
    board[r][c] = "#"
    found = any(dfs(r + dr, c + dc, k + 1) for dr, dc in steps)
    return found
    Fix
    board[r][c] = "#"
    found = any(dfs(r + dr, c + dc, k + 1) for dr, dc in steps)
    board[r][c] = word[k]
    return found

    What happens: In Word Search (LC 79), the mark is the cell itself. The search sets board[r][c] = "#" before it goes to the neighbours. If it does not put the letter back, a later path finds that cell blocked. So a word that is on the board is not found.

    Example: Word Search (LC 79): board = [["A", "B", "B"]], word = "BBA"
    Mistake: returns FalseFix: returns True
Not this one if…
  • Each cell only has to be reached once in total, as in Flood Fill (LC 733), so nothing is undone when the search steps back. See Flood Fill

Deep Dive

Check before placing: one queen per row, three sets for the rest

Permutations already shows the used marks: a choice is marked, the call recurses, and the choice is unmarked. This card adds one rule. Each option is tested against the marks of the options already placed, before it is placed. An option that breaks a rule is skipped, and its whole branch is never built.

In N-Queens (LC 51), the slot is a row. backtrack(r) chooses a column c for row r. One queen per row already rules out two queens in a row, so only columns and diagonals need a test.

A column is identified by c. A down-right diagonal is identified by r - c, because both r and c grow by 1 along it. A down-left diagonal is identified by r + c, because r grows by 1 and c shrinks by 1. So cols, diag and anti are three sets of numbers, and each test is O(1).

Example: a queen at (0, 0) has r - c = 0, and so does (1, 1). A queen at (0, 3) has r + c = 3, and so do (1, 2), (2, 1) and (3, 0). For n = 4, there are 24 ways to put one queen in each row and column. The search places only 16 queens in total and finds 2 boards.

One trace: dead rows and the undo, for n = 4

The board replays solveNQueens(4) until the first board is recorded. The answer for n = 4 is 2 boards. The first one is [".Q..","...Q","Q...","..Q."], and it has queens in columns 1, 3, 0, 2.

The search places (0, 0). In row 1, columns 0 and 1 are attacked, so (1, 2) is placed. In row 2, all four columns are attacked: columns 0 and 2 by cols, column 1 by anti, and column 3 by diag. The call returns, and the undo of (1, 2) removes 2, -1 and 3 from the three sets. This is the step marked as the trap on the board.

Row 1 then takes (1, 3), and row 2 takes (2, 1). In row 3, all four columns are attacked, so the search undoes (2, 1), then (1, 3), then (0, 0). Row 0 moves on to (0, 1). Rows 1, 2 and 3 take columns 3, 0 and 2, r reaches 4, and the first board is recorded.

That is 8 placed queens, 4 undos and 2 dead rows before the first board. The full search for n = 4 places 16 queens in 17 calls of backtrack. It finds the second board ["..Q.","Q...","...Q",".Q.."] after the first.

The undo must reverse every mark that the placement made

A placement writes four things: board[r][c], cols, diag and anti. The undo must write all four back, in any order. Then the state after backtrack(r + 1) is exactly the state before place, so the next column in the loop sees the marks of the queens above row r and nothing else.

A mark that stays is a queen that no longer exists but still blocks. For n = 4, an undo that resets only board[r][c] finds 0 boards instead of 2.

The sets use remove, not discard. If the code is correct, each undo removes a number the matching placement added. A KeyError is an early sign of a bug, such as a test that checks only diag. Then two queens share a down-left diagonal, anti holds the number once, and the second remove fails.

A saved board must be a copy. results.append(["".join(row) for row in board]) builds new strings. Saving board itself stores the list that the undo steps later empty.

Example: for n = 1, the board is [["Q"]] at record time. With results.append(board), the result is [[['.']]] after the undo.

The cost: placements times the cost of one test, and what changes for other puzzles

The card's complexity line is O((B + I + S) * T). B is the number of backtrack calls, I is the number of loop steps, S is the number of complete boards, and T is the cost of one option test, placement or undo. In N-Queens, each of them is O(1) except the copy of a board, so the search time is bounded by O(N!).

The bound is loose. Row r has at most N - r free columns, which gives N! at most. The tests cut far more. For n = 8, 2,056 queens are placed in 2,057 calls, and 92 boards are found. There are 8! = 40,320 ways to put one queen in each row and column.

The board takes O(N^2) space. The three sets and the recursion stack take O(N), and the saved boards are not counted.

Other puzzles use the same loop. N-Queens II (LC 52) asks only for the number of boards, so record adds 1 and no board is copied. Sudoku Solver (LC 37) needs one solved board, so backtrack returns True when the board is full, and the caller must return at once without undoing. Word Search (LC 79) marks a cell before it goes deeper and clears the mark after, so one path cannot reuse a cell.

Example: the same template with the slot as a position in a string and the options ( and ) gives Generate Parentheses (LC 22). The test is that a ) needs an open ( before it.

60-Second Pitch

"I put one queen in each row, so backtrack(r) chooses only a column c for row r. A column is c, a down-right diagonal is r - c, and a down-left diagonal is r + c. I keep each in a set, so a test is O(1). Before I place a queen, I test c, r - c and r + c. If one is taken, I skip that column, and the branch is never built. When r reaches n, I save a copy of the board. After the recursive call, I reset the cell and remove all three numbers. That keeps the marks exact for the next column. The time is bounded by O(N!), and in practice far less. For n = 8, the search places 2,056 queens and finds 92 boards. The space is O(N^2) for the board and O(N) for the sets and the stack."

Common Questions

Why do r - c and r + c identify the diagonals?

Moving down-right adds 1 to both r and c, so r - c does not change. Moving down-left adds 1 to r and subtracts 1 from c, so r + c does not change. Two cells are on the same diagonal exactly when they have the same value.

Why does the code test an option before it places it?

A bad option is then cut off at once, and no deeper call is made for it. If the code tested only complete boards, it would build every filling first. For n = 8, the early test places 2,056 queens, and one queen per row with no early test gives 8^8 = 16,777,216 fillings to check.

How do you change this to count boards, as in N-Queens II (LC 52)?

Replace the copy of the board with count += 1. The three sets already carry all the information needed for the test, so board is not needed at all. This saves the O(N^2) copy per board.

What changes for Sudoku Solver (LC 37)?

The slot is the next empty cell, the options are the digits 1 to 9, and the marks are a set for each row, column and box. The question asks for one solution, so backtrack returns True when the board is full, and every caller returns True at once without undoing.

How can the three sets be made faster?

Store each set as the bits of one integer, so a test is one bitwise operation, and a placement sets three bits. The search still places the same queens. Only the constant factor drops. Say that in an interview after the sets version works.

SWE Reasoning

Spot it

  • A board or a string is filled one position at a time, and each position takes one value from a small set.
  • A rule links a new value to the values already placed: no two queens attack, no repeated digit in a row, no ) without an open (.
  • A rule can be tested the moment a value is placed, so a bad value is known at once.
  • The question asks for every valid filling, the number of them, or one valid filling.
  • On a board of cells, a path must not use the same cell twice, as in Word Search (LC 79).

The invariant, step by step

  1. backtrack(r) is called when rows 0 to r - 1 each hold one queen, and no two of them attack each other.
  2. cols, diag and anti hold exactly the columns and the two kinds of diagonals of those queens.
  3. If r == n, all n rows have a queen and every queen passed the test when it was placed. The call records a copy of the board.
  4. Otherwise the loop tries each column c. If c, r - c or r + c is in its set, a placed queen attacks the cell, and the loop continues with the next column.
  5. If the cell is free, the code sets board[r][c] and adds c, r - c and r + c to the sets. The invariant now holds for r + 1, and backtrack(r + 1) runs.
  6. After the call, the code resets board[r][c] and removes the three numbers. The state is again as it was before the placement, so the next column starts from the same marks.

Traps

  • An undo that skips the sets. A queen that was taken back still blocks its column and diagonals. For n = 4, the result is 0 boards instead of 2.
  • A test of only one diagonal. Two queens can share a down-left diagonal. The set anti holds the number once, and the second remove raises KeyError.
  • Saving board itself. The undo steps empty the saved board. Save a copy of the rows as strings.
  • Testing after the whole board is built. The search then builds every filling before it rejects one. Test each option before place.
  • Ignoring the return value in Sudoku Solver (LC 37). The full board returns True. If the caller does not stop, the undo clears the last cell and the solution is lost.

Practice

Canonical LeetCode:N-Queens (LC 51)Sudoku Solver (LC 37)Unique Paths III (LC 980)Path with Maximum Gold (LC 1219)Word Search II (LC 212)Android Unlock Patterns (LC 351)Generate Parentheses (LC 22)Word Search (LC 79)Matchsticks to Square (LC 473)The k-th Lexicographical String of All Happy Strings of Length n (LC 1415)N-Queens II (LC 52)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•