N-Queens II (Count Solutions)
Start Backtracking at Row 0
The algorithm begins at row 0 with empty columns and diagonals (all zero). It prepares to find all valid queen placements starting from the first row.
def backtrack(row, cols, pos_diags, neg_diags):
if row == n:
return 1
count = 0Calculate Available Positions at Row 0
Compute available positions by masking out columns and diagonals under attack. Since all are zero, all columns are available (bitmask 1111).
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)Place Queen at Row 0, Column 0
Select the rightmost available position (column 0) and place a queen there, updating bitmasks to mark attacked columns and diagonals.
position = available_positions & -available_positions
available_positions &= available_positions - 1
count += backtrack(row + 1, cols | position, (pos_diags | position) << 1, (neg_diags | position) >> 1)Calculate Available Positions at Row 1
Calculate available positions for row 1 by masking out attacked columns and diagonals from previous queen placements.
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)Place Queen at Row 1, Column 2
Choose the rightmost available position (column 2) at row 1 and place a queen, updating bitmasks accordingly.
position = available_positions & -available_positions
available_positions &= available_positions - 1
count += backtrack(row + 1, cols | position, (pos_diags | position) << 1, (neg_diags | position) >> 1)Calculate Available Positions at Row 2
Calculate available positions for row 2 by masking out attacked columns and diagonals from previous queens.
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)Place Queen at Row 2, Column 1
Place queen at the only available position (column 1) in row 2, updating bitmasks for next row.
position = available_positions & -available_positions
available_positions &= available_positions - 1
count += backtrack(row + 1, cols | position, (pos_diags | position) << 1, (neg_diags | position) >> 1)Calculate Available Positions at Row 3
Calculate available positions for row 3 by masking out attacked columns and diagonals.
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)Place Queen at Row 3, Column 3 and Complete Solution
Place queen at column 3 in row 3, reaching the base case where all rows have queens placed, so count one solution.
if row == n:
return 1Backtrack to Row 3 and Try Next Position
Backtrack from row 4 to row 3 to try other available positions. No other positions remain at row 3, so backtrack further.
available_positions &= available_positions - 1Backtrack to Row 2 and Try Next Position
Backtrack to row 2 to try other available positions. No other positions remain at row 2, so backtrack further.
available_positions &= available_positions - 1Place Queen at Row 1, Column 3
Try the next available position at row 1, column 3, and recurse to row 2 with updated bitmasks.
position = available_positions & -available_positions
available_positions &= available_positions - 1
count += backtrack(row + 1, cols | position, (pos_diags | position) << 1, (neg_diags | position) >> 1)Calculate Available Positions at Row 2
Calculate available positions for row 2 with updated bitmasks after placing queen at (1,3).
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)Place Queen at Row 2, Column 1
Place queen at column 1 in row 2 and recurse to row 3 with updated bitmasks.
position = available_positions & -available_positions
available_positions &= available_positions - 1
count += backtrack(row + 1, cols | position, (pos_diags | position) << 1, (neg_diags | position) >> 1)Calculate Available Positions at Row 3
Calculate available positions for row 3 after placing queens at previous rows.
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)Place Queen at Row 3, Column 3 and Complete Second Solution
Place queen at column 3 in row 3, reaching the base case and counting the second valid solution.
if row == n:
return 1Backtrack to Row 1 and Try Next Position
Backtrack to row 1 after exhausting all options at row 3, then try next available position at row 1 (column 1).
available_positions &= available_positions - 1No More Positions at Row 1, Backtrack to Row 0
After trying all positions at row 1, no more options remain, so backtrack to row 0 to try next queen placement.
available_positions &= available_positions - 1Place Queen at Row 0, Column 1
Try placing queen at column 1 in row 0 and recurse to row 1 with updated bitmasks.
position = available_positions & -available_positions
available_positions &= available_positions - 1
count += backtrack(row + 1, cols | position, (pos_diags | position) << 1, (neg_diags | position) >> 1)End of Search, Return Total Solutions
All recursive calls complete, returning the total count of 2 valid solutions for the 4-queens problem.
return backtrack(0, 0, 0, 0)class Solution:
def totalNQueens(self, n: int) -> int:
def backtrack(row, cols, pos_diags, neg_diags):
# STEP 1: Check if all rows are placed
if row == n:
return 1
count = 0
# STEP 2: Calculate available positions
available_positions = ((1 << n) - 1) & ~(cols | pos_diags | neg_diags)
while available_positions:
# STEP 3: Extract rightmost available position
position = available_positions & -available_positions
# STEP 4: Remove chosen position
available_positions &= available_positions - 1
# STEP 5: Recurse with updated bitmasks
count += backtrack(row + 1,
cols | position,
(pos_diags | position) << 1,
(neg_diags | position) >> 1)
return count
return backtrack(0, 0, 0, 0)Key Takeaways
This insight is hard to see from code alone because bit operations are compact and abstract, but visualization shows how constraints evolve.
Seeing the tree helps understand the exhaustive search and pruning, which is less obvious from reading code.
Understanding that counting solutions is about counting leaf nodes clarifies the base case and return values.
