N-Queens Problem: Backtracking Solution with Optimizations

The N-Queens problem is the classic backtracking problem. It places N chess queens on an N×N board so no two queens attack each other. Mastering it teaches you the backtracking template that applies to Sudoku, permutations, combinations, and constraint satisfaction problems.

Problem

Place N queens on an N×N chessboard such that no two queens share the same row, column, or diagonal. Return all valid placements.

Backtracking Solution

def solve_n_queens(n: int) -> list[list[str]]:
    """
    Backtracking: place one queen per row, track attacked columns/diagonals.
    Time: O(N!), Space: O(N) for the recursion stack
    """
    results = []
    # Track which columns and diagonals are under attack
    cols = set()
    diag1 = set()  # top-left to bottom-right: (row - col) is constant
    diag2 = set()  # top-right to bottom-left: (row + col) is constant

    board = [['.' for _ in range(n)] for _ in range(n)]

    def backtrack(row):
        if row == n:
            # All queens placed — record this solution
            results.append([''.join(r) for r in board])
            return

        for col in range(n):
            if col in cols or (row - col) in diag1 or (row + col) in diag2:
                continue  # This cell is under attack

            # Place queen
            board[row][col] = 'Q'
            cols.add(col)
            diag1.add(row - col)
            diag2.add(row + col)

            backtrack(row + 1)  # Recurse to next row

            # Remove queen (backtrack)
            board[row][col] = '.'
            cols.remove(col)
            diag1.remove(row - col)
            diag2.remove(row + col)

    backtrack(0)
    return results

solutions = solve_n_queens(4)
for sol in solutions:
    for row in sol:
        print(row)
    print()
# Two solutions for N=4

Count-Only Version (LeetCode 52)

def total_n_queens(n: int) -> int:
    """Just count solutions without recording board states."""
    count = [0]
    cols = set()
    diag1 = set()
    diag2 = set()

    def backtrack(row):
        if row == n:
            count[0] += 1
            return
        for col in range(n):
            if col in cols or (row - col) in diag1 or (row + col) in diag2:
                continue
            cols.add(col); diag1.add(row - col); diag2.add(row + col)
            backtrack(row + 1)
            cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)

    backtrack(0)
    return count[0]

print(total_n_queens(8))  # 92

Bit Manipulation Optimization

For large N, represent attacked columns and diagonals as bitmasks for O(1) set operations:

def solve_n_queens_bits(n: int) -> int:
    """
    Bit manipulation: cols, diag1, diag2 as integers.
    Attacked positions = set bits.
    Time: O(N!), but constant factors are much smaller than set-based approach.
    """
    count = [0]
    limit = (1 << n) - 1  # All N bits set = all columns occupied

    def backtrack(cols, diag1, diag2):
        if cols == limit:
            count[0] += 1
            return

        # Available positions: not attacked by cols, diag1, or diag2
        available = limit & ~(cols | diag1 | diag2)

        while available:
            pos = available & (-available)  # Isolate rightmost set bit
            available &= available - 1      # Remove rightmost set bit

            backtrack(
                cols | pos,
                (diag1 | pos) <> 1
            )

    backtrack(0, 0, 0)
    return count[0]

The Backtracking Template

N-Queens demonstrates the universal backtracking pattern:

def backtrack(state):
    if is_solution(state):
        record(state)
        return

    for choice in get_choices(state):
        if is_valid(state, choice):
            apply(state, choice)      # Make choice
            backtrack(state)           # Recurse
            undo(state, choice)        # Undo choice (backtrack)

This same template solves: Sudoku, generate permutations/combinations/subsets, word search on a grid, and all constraint satisfaction problems.

Related Recursion Topics

  • Generate All Subsets — both use the same backtracking template; subsets has no constraints, N-Queens has column/diagonal constraints
Scroll to Top