Begin Algo
Recursion & Backtracking · 04 / 05

N-QueensN-queens

Place row by row, tracking attacked columns and diagonals in sets.

Used for: The prototype constraint-satisfaction problem: timetabling, rostering

Time complexityExponential
Space complexityO(n)
DifficultyHard
PrerequisitesCombinations & Combination Sum, Hash Table

01Why it exists

Building a timetable automatically

Every course needs a slot and a room. One teacher cannot take two courses at once, one room cannot hold two classes at once, and certain courses must not land on the same day. With well over a hundred courses, doing it by hand takes weeks.

Why this fitsHandle one course at a time: pick a slot from the ones still free, move on to the next course, and if every slot clashes, go back to the previous course and give it a different slot. N-Queens is the smallest textbook version of this kind of "constraint satisfaction problem": one queen per row, never sharing a column or a diagonal with a queen already placed.

Shift rotas and seating plans

A nursing station runs three shifts a day. Each person has days they cannot work, a limit on consecutive shifts, and colleagues they must not be paired with. You need a rota that satisfies every rule.

Why this fitsFill one cell, check every rule against it, and go back and change the previous cell when a rule breaks: that is exactly backtracking. The crucial part is that the conflict check has to be fast. N-Queens squeezes each check down to O(1) with three sets, and a rota uses the same idea with pre-built "who is busy when" tables.

Sudoku and crossword solvers

A Sudoku app has to confirm that any given grid is solvable and be able to offer a hint. The human method is to fill in a square, see whether anything contradicts, and rub it out and try again if it does.

Why this fitsThe program does exactly what the human does: try 1 through 9 in each square, check for conflicts against three sets of constraints for the row, the column and the box, and backtrack when the square leads nowhere. Sudoku is a direct extension of N-Queens; only the shape of the constraints differs.

Reach for it when you see:Nothing may clash, one per row and one per column, timetabling and rotas, constraint satisfaction, a check after each placement, going back a step when stuck, Sudoku.

02The core idea

N-Queens asks you to place n queens on an n×n board so that no two of them share a row, a column or a diagonal. The first insight is exactly one queen per row: since two queens cannot share a row and there are n of them, every row holds precisely one, so the only decision left is which column the queen in row r takes. The search space shrinks from "choose any n squares" to "choose one column per row", which is a very different size of problem.

The second insight is that the conflict check has to be O(1). Squares in the same column share the same c; squares on the same "\" diagonal share the same r−c; squares on the same "/" diagonal share the same r+c. Keep three sets holding the c, r−c and r+c of the queens already placed, and deciding whether a square is safe is three lookups. Placing a queen adds all three values, and taking it back has to remove all three — miss one and every later branch reads the board wrongly.

The search itself is backtracking's three steps: for each column c of row r, skip the square if it is attacked (pruning), otherwise make the choice, recurse into row r+1, and undo. If every column of row r+1 is attacked, the recursion simply returns, and that is the moment of backtracking: lift the queen off row r and move it to the next column. The whole thing is a DFS over a tree of depth n whose branching factor at each node is the number of columns in that row not yet under attack.

The cost is exponential, with a loose upper bound of O(n!), although pruning brings the real figure far below that: 8 queens has 92 solutions and the search visits roughly two thousand nodes. When you only need the count and not the boards themselves, bitmasks can replace the sets: bit c of each of three integers says whether column c is attacked, and moving to the next row shifts the whole "\" mask one place left and the "/" mask one place right. This is the fastest way to write N-Queens, and it is the textbook example of using an integer as a set.

03The algorithm

  1. 1Set up queens (the column used by each row) and three sets: cols, diag1 (r−c) and diag2 (r+c). dfs(r) means "currently filling row r".
  2. 2Base case: r == n means all n rows are filled, so turn queens into a board and add it to the answers.
  3. 3For each column c: if c in cols or r-c in diag1 or r+c in diag2, the square is attacked, so skip it.
  4. 4Make the choice: queens.append(c), add one value to each of the three sets, and recurse with dfs(r + 1).
  5. 5Undo the choice: queens.pop() and remove one value from each of the three sets. When every column of this row has been tried without success, the call simply returns to the previous row — and that is backtracking.

04Interactive demo

Four queens, filled one row at a time. A pale yellow square is attacked by a queen already on the board, and every time the search lands on an attacked square it says which constraint was violated. When every column of a row is attacked, the queen on the row above is lifted and moved to its next column, and so on until the first solution appears.

start4 queens · one row at a time
Occupied sets
cols
empty
r−c
empty
r+c
empty

A pale yellow square is attacked by a queen already on the board; the darker yellow one is the square being tried in this step. The grey row is the row currently being filled. Every test is three set lookups, O(1).

Step 0/31Place 4 queens on the 4×4 board so that no two share a row, a column or a diagonal. One queen goes in each row, so the only decision is which column that row uses. Three sets record the columns and the two diagonal families that are already taken: r−c runs top-left to bottom-right, r+c runs top-right to bottom-left.

05Code

The set-based version that lists every solution, and the bitmask version that only counts them.

# N-Queens (LeetCode 51): one row at a time, three sets tracking the attacked columns and diagonals
def solve_n_queens(n):
    ans = []
    queens = []                  # queens[r] = the column of the queen in row r
    cols = set()                 # columns already taken
    diag1 = set()                # cells with the same r - c lie on the same "\" diagonal
    diag2 = set()                # cells with the same r + c lie on the same "/" diagonal

    def dfs(r):
        if r == n:               # every row has its queen
            ans.append(["." * c + "Q" + "." * (n - c - 1) for c in queens])
            return
        for c in range(n):
            if c in cols or (r - c) in diag1 or (r + c) in diag2:
                continue         # attacked, so prune it
            queens.append(c)     # make the choice
            cols.add(c); diag1.add(r - c); diag2.add(r + c)
            dfs(r + 1)
            queens.pop()         # undo the choice: all three sets must be restored
            cols.remove(c); diag1.remove(r - c); diag2.remove(r + c)

    dfs(0)
    return ans


# Counting solutions only (LeetCode 52): bitmasks instead of sets
# cols / d1 / d2 are n-bit integers; bit c set means column c of this row is attacked
def total_n_queens(n):
    full = (1 << n) - 1

    def dfs(cols, d1, d2):
        if cols == full:                     # every column holds a queen
            return 1
        count = 0
        free = full & ~(cols | d1 | d2)      # the squares still open in this row
        while free:
            bit = free & -free               # take the lowest set bit
            free ^= bit
            # next row: the \ diagonals move one column right (<< 1), the / diagonals one column left (>> 1)
            count += dfs(cols | bit, ((d1 | bit) << 1) & full, (d2 | bit) >> 1)
        return count

    return dfs(0, 0, 0)


if __name__ == "__main__":
    for row in solve_n_queens(4)[0]:
        print(row)                           # .Q.. / ...Q / Q... / ..Q.
    print(total_n_queens(8))                 # 92

06Practice

  • LeetCode 36Valid Sudoku (practise the conflict check first)Medium
  • LeetCode 473Matchsticks to Square (every matchstick joins one of four sides; sort, then prune)Medium
  • LeetCode 51N-QueensHard
  • LeetCode 52N-Queens II (bitmasks)Hard
  • LeetCode 37Sudoku Solver (three sets: rows, columns and boxes)Hard
  • LeetCode 1655Distribute Repeating Integers (constraint satisfaction plus pruning)Hard