N-Queens II
Hard · Backtracking · Explanation and code example
Backtracking (Bit Mask)
How it works
Bit manipulation offers the most compact representation for tracking occupied columns and diagonals. We use three integers as bitmasks: each bit in col represents whether that column is occupied, and similarly for the two diagonal masks. Checking if a position is attacked becomes a single bitwise AND operation. Setting and unsetting bits is done with XOR. This approach is both space-efficient and cache-friendly.
Steps
- Initialize three integers
col,posDiag, andnegDiagto0. - Define a backtracking function that takes the current
r(row):- If
requalsn, increment the solution count and return. - For each
c(column) in the row:- Check if the bit at position
cincol, positionr + cinposDiag, or positionr - c + ninnegDiagis set. If any is set, skip this column. - Toggle the corresponding bits using XOR.
- Recursively call backtrack for the next row.
- Toggle the bits again to restore the previous state (backtrack).
- Check if the bit at position
- If
- Start backtracking from row
0and return the final count.
Complexity
- Time complexity:
O(n!) - Space complexity:
O(n)for recursion stack.
Things to watch for
Checking Only Columns and Forgetting Diagonals
A frequent mistake is checking only whether a column is occupied while neglecting diagonal conflicts. Queens attack along both diagonals, so you must verify the upper-left diagonal (where row - col is constant) and the upper-right diagonal (where row + col is constant). Missing either diagonal check will produce incorrect solution counts.
Incorrect Diagonal Index Calculation
When using arrays to track diagonal occupancy, the negative diagonal row - col can produce negative indices. You must offset this value by adding n (i.e., use row - col + n) to ensure valid array indices. Forgetting this offset causes array index out of bounds errors or incorrect diagonal tracking.
Forgetting to Backtrack State
After recursively exploring a placement and returning, you must undo the state changes (remove the queen from sets/arrays and reset the board position). Failing to properly backtrack leaves stale state that incorrectly blocks future valid placements, causing the algorithm to miss solutions or count incorrectly.
Scanning Below Current Row in Safety Check
Since queens are placed row by row from top to bottom, only rows above the current row can contain previously placed queens. A common error is scanning the entire column or both diagonal directions (up and down). This is wasteful at best and can cause incorrect behavior if the board is not properly initialized. Only check upward directions: the column above, upper-left diagonal, and upper-right diagonal.
Python code example
class Solution:
def totalNQueens(self, n: int) -> int:
col = 0
posDiag = 0
negDiag = 0
res = 0
def backtrack(r):
nonlocal col, posDiag, negDiag, res
if r == n:
res += 1
return
for c in range(n):
if ((col & (1 << c)) or (posDiag & (1 << (r + c)))
or (negDiag & (1 << (r - c + n)))):
continue
col ^= (1 << c)
posDiag ^= (1 << (r + c))
negDiag ^= (1 << (r - c + n))
backtrack(r + 1)
col ^= (1 << c)
posDiag ^= (1 << (r + c))
negDiag ^= (1 << (r - c + n))
backtrack(0)
return res
C++ code example
class Solution {
public:
int col = 0, posDiag = 0, negDiag = 0;
vector<string> board;
int totalNQueens(int n) {
int res = 0;
backtrack(0, n, res);
return res;
}
void backtrack(int r, int n, int& res) {
if (r == n) {
res++;
return;
}
for (int c = 0; c < n; c++) {
if ((col & (1 << c)) || (posDiag & (1 << (r + c))) ||
(negDiag & (1 << (r - c + n)))) {
continue;
}
col ^= (1 << c);
posDiag ^= (1 << (r + c));
negDiag ^= (1 << (r - c + n));
backtrack(r + 1, n, res);
col ^= (1 << c);
posDiag ^= (1 << (r + c));
negDiag ^= (1 << (r - c + n));
}
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.