Skip to content

N-Queens II

LeetCode: 52. N-Queens IIDifficulty: Hard Pattern: Backtracking (Count Only)


Problem Statement

The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.

Given an integer n, return the number of distinct solutions to the n-queens puzzle.


Examples

Example 1

text
Input: n = 4
Output: 2
Explanation: There are two distinct solutions to the 4-queens puzzle.

Example 2

text
Input: n = 1
Output: 1

Constraints

  • 1 <= n <= 9

Key Difference from N-Queens

AspectN-QueensN-Queens II
ReturnAll board configurationsJust the count
Build boardYesNo
ComplexitySameSlightly simpler code

Since we only need to count, we don't need to construct the actual board!


Solutions

Solution

python
def totalNQueens(n: int) -> int:
    """
    Count N-Queens solutions using backtracking.

    Simpler than N-Queens since we don't build boards.

    Time Complexity: O(n!)
    Space Complexity: O(n)
    """
    cols = set()
    pos_diag = set()
    neg_diag = set()

    def backtrack(row):
        if row == n:
            return 1  # Found one solution

        count = 0
        for col in range(n):
            if col in cols or (row + col) in pos_diag or (row - col) in neg_diag:
                continue

            # Place queen
            cols.add(col)
            pos_diag.add(row + col)
            neg_diag.add(row - col)

            # Count solutions from this placement
            count += backtrack(row + 1)

            # Remove queen
            cols.remove(col)
            pos_diag.remove(row + col)
            neg_diag.remove(row - col)

        return count

    return backtrack(0)


# Test
print(totalNQueens(4))  # Output: 2
print(totalNQueens(8))  # Output: 92
java
public int totalNQueens(int n) {
    boolean[] cols = new boolean[n];
    boolean[] posDiag = new boolean[2 * n]; // row + col
    boolean[] negDiag = new boolean[2 * n]; // row - col + n (offset)
    return backtrack(cols, posDiag, negDiag, 0, n);
}

private int backtrack(boolean[] cols, boolean[] posDiag, boolean[] negDiag,
                      int row, int n) {
    if (row == n) return 1;
    int count = 0;
    for (int col = 0; col < n; col++) {
        int pd = row + col;
        int nd = row - col + n;
        if (cols[col] || posDiag[pd] || negDiag[nd]) continue;
        cols[col] = true;
        posDiag[pd] = true;
        negDiag[nd] = true;
        count += backtrack(cols, posDiag, negDiag, row + 1, n);
        cols[col] = false;
        posDiag[pd] = false;
        negDiag[nd] = false;
    }
    return count;
}

Complexity: Time O(n!) · Space O(n)

  • Time: At each row, available columns are reduced by constraints. Upper bound is n! placements explored due to column and diagonal restrictions.
  • Space: Three sets store at most n elements each, plus O(n) recursion depth. No board storage needed since we only count.

Solution 2: Using Nonlocal Counter

python
def totalNQueens_nonlocal(n: int) -> int:
    """
    Alternative using nonlocal counter variable.

    Time Complexity: O(n!)
    Space Complexity: O(n)
    """
    count = 0
    cols = set()
    pos_diag = set()
    neg_diag = set()

    def backtrack(row):
        nonlocal count

        if row == n:
            count += 1
            return

        for col in range(n):
            if col in cols or (row + col) in pos_diag or (row - col) in neg_diag:
                continue

            cols.add(col)
            pos_diag.add(row + col)
            neg_diag.add(row - col)

            backtrack(row + 1)

            cols.remove(col)
            pos_diag.remove(row + col)
            neg_diag.remove(row - col)

    backtrack(0)
    return count


# Test
print(totalNQueens_nonlocal(4))  # Output: 2

Complexity: Time O(n!) · Space O(n)

  • Time: Same exploration pattern as Solution 1; nonlocal counter is simply a different way to accumulate the count.
  • Space: Three sets for constraint tracking, plus recursion depth of n. The nonlocal counter uses O(1) additional space.

Solution 3: Bitmask (Fastest)

python
def totalNQueens_bitmask(n: int) -> int:
    """
    Bit manipulation for maximum speed.

    Time Complexity: O(n!)
    Space Complexity: O(n)
    """
    def backtrack(row, cols, pos_diag, neg_diag):
        if row == n:
            return 1

        count = 0
        # Available positions: bits that are 0 in all three masks
        available = ((1 << n) - 1) & ~(cols | pos_diag | neg_diag)

        while available:
            # Get the rightmost 1 bit
            pos = available & (-available)
            # Remove it from available
            available &= available - 1

            # Recurse with updated masks
            # pos_diag shifts left (up-right), neg_diag shifts right (up-left)
            count += backtrack(
                row + 1,
                cols | pos,
                (pos_diag | pos) << 1,
                (neg_diag | pos) >> 1
            )

        return count

    return backtrack(0, 0, 0, 0)


# Test
print(totalNQueens_bitmask(4))   # Output: 2
print(totalNQueens_bitmask(8))   # Output: 92
print(totalNQueens_bitmask(9))   # Output: 352

Complexity: Time O(n!) · Space O(n)

  • Time: Bitmask operations (AND, OR, shift) are O(1), making conflict detection faster than set lookups in practice.
  • Space: Three integers (bitmasks) replace the sets, using O(1) extra space. Recursion depth remains O(n).

Solution 4: Memoization Attempt (Interesting but not effective)

python
def totalNQueens_memo(n: int) -> int:
    """
    Attempting memoization - but state space is too large.

    This actually doesn't help much for N-Queens because
    the state (cols, pos_diag, neg_diag) rarely repeats.

    Included to show why N-Queens isn't easily memoized.
    """
    memo = {}

    def backtrack(row, cols, pos_diag, neg_diag):
        if row == n:
            return 1

        state = (row, cols, pos_diag, neg_diag)
        if state in memo:
            return memo[state]

        count = 0
        for col in range(n):
            col_bit = 1 << col
            if cols & col_bit or pos_diag & col_bit or neg_diag & col_bit:
                continue

            count += backtrack(
                row + 1,
                cols | col_bit,
                (pos_diag | col_bit) << 1,
                (neg_diag | col_bit) >> 1
            )

        memo[state] = count
        return count

    return backtrack(0, 0, 0, 0)


# Test
print(totalNQueens_memo(4))  # Output: 2

Complexity: Time O(n!) · Space O(n!)

  • Time: Same O(n!) exploration; memoization rarely helps because the state (row, cols, diagonals) rarely repeats across different search paths.
  • Space: The memo dictionary can grow large but states rarely repeat, so it mostly adds overhead rather than benefit.

Solution 5: Precomputed Lookup (For Competitive Programming)

python
def totalNQueens_lookup(n: int) -> int:
    """
    Precomputed values for instant O(1) lookup.

    Useful when you need to answer many queries quickly.
    """
    solutions = {
        1: 1,
        2: 0,
        3: 0,
        4: 2,
        5: 10,
        6: 4,
        7: 40,
        8: 92,
        9: 352,
        10: 724,
        11: 2680,
        12: 14200,
        13: 73712,
        14: 365596,
        15: 2279184,
    }
    return solutions.get(n, 0)


# Test
print(totalNQueens_lookup(8))  # Output: 92

Complexity: Time O(1) · Space O(1)

  • Time: Direct dictionary lookup using precomputed values; no computation at runtime.
  • Space: The dictionary is constant size (known values up to n=15 or so), stored statically.

Complexity Analysis

ApproachTimeSpaceNotes
SetsO(n!)O(n)Clear and readable
BitmaskO(n!)O(n)Fastest in practice
MemoizationO(n!)O(n!)State rarely repeats
LookupO(1)O(1)For precomputed values

Bit Manipulation Explained

text
Example for n=4

available = ~(cols | pos_diag | neg_diag) & ((1 << n) - 1)
If cols=0010, pos_diag=0100, neg_diag=0001
blocked = 0010 | 0100 | 0001 = 0111
available = ~0111 & 1111 = 1000 (only column 3 is free)

Get rightmost bit: pos = available & (-available)
For available = 1000: pos = 1000 & 0111+1 = 1000

Remove rightmost bit: available &= available - 1
For available = 1000: 1000 & 0111 = 0000

Diagonal shifts for next row:
pos_diag << 1: attacks move left (from queen's perspective going up)
neg_diag >> 1: attacks move right

Number of Solutions Table

nSolutions
11
20
30
42
510
64
740
892
9352
10724

When to Use N-Queens II vs N-Queens

ScenarioUse
Need actual board layoutsN-Queens
Just need countN-Queens II
Interview asks for "all solutions"N-Queens
Interview asks "how many"N-Queens II

Common Mistakes

1. Returning Wrong Type

python
# WRONG - returns list instead of int
def wrong(n):
    results = []
    def backtrack(row):
        if row == n:
            results.append(True)  # Should just count!
    return results  # Returns list, not int

# CORRECT
def correct(n):
    def backtrack(row):
        if row == n:
            return 1  # Return count
        # ...
        return count

2. Forgetting to Return Count from Recursion

python
# WRONG - doesn't propagate count
def backtrack(row):
    count = 0
    for col in range(n):
        if valid:
            backtrack(row + 1)  # Missing: count += ...
    return count

# CORRECT
def backtrack(row):
    count = 0
    for col in range(n):
        if valid:
            count += backtrack(row + 1)  # Accumulate!
    return count

N-Queens (LeetCode 51)

Problem: Return all distinct solutions to the n-queens puzzle as board configurations.

Key Insight: Same constraint logic, but builds actual board representations.

Approach: Same backtracking with sets/bitmasks, but construct board strings at base case.

Complexity: O(n!) time, O(n^2) space for boards

Link: Local Solution

Sudoku Solver (LeetCode 37)

Problem: Solve a Sudoku puzzle by filling the empty cells.

Key Insight: Same constraint satisfaction pattern - track valid placements and backtrack.

Approach: Track row/col/box constraints, try digits 1-9, backtrack on invalid state.

Complexity: O(9^empty_cells) worst case

Link: Local Solution

Valid Sudoku (LeetCode 36)

Problem: Check if a 9x9 Sudoku board is valid (no duplicate in row/col/3x3 box).

Key Insight: Validation only - no backtracking needed, just constraint checking.

Approach: Use 9 sets for rows, 9 for columns, 9 for boxes. Single pass to check duplicates.

Complexity: O(81) = O(1) time, O(81) = O(1) space

Link: LeetCode 36


Interview Tips

  1. Mention it's simpler than N-Queens since no board building
  2. Return count from recursion - cleaner than using global/nonlocal
  3. Know the bitmask approach for performance discussions
  4. Memorize a few values: 4->2, 8->92 to verify your solution
  5. Explain why memoization doesn't help much here

Key Takeaways

  1. Simpler than N-Queens - just count, don't build boards
  2. Return count from recursion for clean code
  3. Bitmask is fastest but harder to explain
  4. Same O(n!) complexity as N-Queens
  5. Good interview warmup before full N-Queens

Last updated: January 2026