Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

N-Queens Problem: Five Recursive Approaches with Progressive Optimizations

Notes 1

Problem Statement

Place N queens on an N×N chessboard such that no two queens threaten each other — i.e., no two share the same row, column, or diagoanl.

1. Basic Backtracking

A straightforward recursive backtracking approach places one queen per row and validates column and diagonal constraints before proceeding.

int countNQueens(int n) {
    if (n == 1) return 1;
    std::vector<int> board(n + 1, 0);
    int solutions = 0;

    auto isValid = [&](int row, int col) -> bool {
        for (int r = 1; r < row; ++r) {
            int c = board[r];
            if (c == col || std::abs(c - col) == std::abs(r - row)) {
                return false;
            }
        }
        return true;
    };

    std::function<void(int)> backtrack = [&](int row) {
        if (row > n) {
            ++solutions;
            return;
        }
        for (int col = 1; col <= n; ++col) {
            if (isValid(row, col)) {
                board[row] = col;
                backtrack(row + 1);
                board[row] = 0;
            }
        }
    };

    backtrack(1);
    return solutions;
}

Time complexity: O(N^N) in worst case. Space: O(N).

2. Horizontal Symmetry Pruning

Exploit left-right symmetry: for even N, restrict the first queen to columns 1 through N/2; for odd N, restrict to columns 1 through (N+1)/2, and when placing at the center column, constrain the second queen to the left half to avoid double-counting symmetric solutions.

int countWithSymmetry(int n) {
    if (n == 1) return 1;
    std::vector<int> board(n + 1, 0);
    int solutions = 0;
    const int mid = (n + 1) / 2;
    const bool isOdd = n % 2 == 1;

    auto isValid = [&](int row, int col) -> bool {
        for (int r = 1; r < row; ++r) {
            int c = board[r];
            if (c == col || std::abs(c - col) == std::abs(r - row)) {
                return false;
            }
        }
        return true;
    };

    std::function<void(int, int)> backtrack = [&](int row, int maxCol) {
        if (row > n) {
            ++solutions;
            return;
        }
        int upperBound = (row == 1) ? mid : n;
        if (row == 2 && isOdd && board[1] == mid) {
            upperBound = mid;
        }
        for (int col = 1; col <= upperBound; ++col) {
            if (isValid(row, col)) {
                board[row] = col;
                backtrack(row + 1, n);
                board[row] = 0;
            }
        }
    };

    backtrack(1, mid);
    return (n % 2 == 0) ? solutions * 2 : solutions * 2 - countCenterFixed(n);
}

3. Conflict Tracking via Boolean Arrays

Replace repeated validity checks with three boolean arrays tracking ocucpied columns and both diagonals (indexed as row - col and row + col). Eliminates O(N) scan per placement.

int countWithArrays(int n) {
    std::vector<bool> colUsed(n + 1, false);
    std::vector<bool> diag1Used(2 * n + 1, false); // row - col + n
    std::vector<bool> diag2Used(2 * n + 1, false); // row + col
    int solutions = 0;

    std::function<void(int)> backtrack = [&](int row) {
        if (row > n) {
            ++solutions;
            return;
        }
        for (int col = 1; col <= n; ++col) {
            int d1 = row - col + n, d2 = row + col;
            if (!colUsed[col] && !diag1Used[d1] && !diag2Used[d2]) {
                colUsed[col] = diag1Used[d1] = diag2Used[d2] = true;
                backtrack(row + 1);
                colUsed[col] = diag1Used[d1] = diag2Used[d2] = false;
            }
        }
    };

    backtrack(1);
    return solutions;
}

4. Bitmask-Based Diagonal Encoding

Represent column and diagonal occupancy using integers: cols, diag1, diag2. Shift and mask operations replace array lookups. Enables compact state propagation.

int countWithBitmasks(int n) {
    int solutions = 0;
    const int full = (1 << n) - 1;

    std::function<void(int, int, int, int)> backtrack = 
        [&](int row, int cols, int diag1, int diag2) {
        if (cols == full) {
            ++solutions;
            return;
        }
        int available = full & ~(cols | diag1 | diag2);
        while (available) {
            int pos = available & -available; // lowest set bit
            available ^= pos;
            int newCols = cols | pos;
            int newDiag1 = (diag1 | pos) << 1;
            int newDiag2 = (diag2 | pos) >> 1;
            backtrack(row + 1, newCols, newDiag1, newDiag2);
        }
    };

    backtrack(1, 0, 0, 0);
    return solutions;
}

5. Full Bitwise Optimization (Iterative State Packing)

Merge all three constraints into a single recursive call with precomputed bit masks. Use compiler-friendly bit logic and eliminate recursion overhead where possible (though still recursive here for clarity).

int countOptimizedBits(int n) {
    if (n == 0) return 1;
    int solutions = 0;
    const int mask = (1 << n) - 1;

    std::function<void(int, int, int, int)> search = 
        [&](int row, int cols, int ldiag, int rdiag) {
        if (cols == mask) {
            ++solutions;
            return;
        }
        int free = mask & ~(cols | ldiag | rdiag);
        while (free) {
            int bit = free & -free;
            free ^= bit;
            search(row + 1,
                   cols | bit,
                   (ldiag | bit) << 1,
                   (rdiag | bit) >> 1);
        }
    };

    search(0, 0, 0, 0);
    return solutions;
}

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.