N-Queens
Start Backtracking at Row 0
The algorithm begins at row 0 with empty board and no columns or diagonals attacked (cols=0, diag1=0, diag2=0).
backtrack(0, 0, 0, 0)Calculate Available Positions at Row 0
Compute available_positions by negating attacked columns and diagonals and masking with (1 << n) - 1 to keep only n bits.
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))Place Queen at Row 0, Column 0
Extract rightmost 1-bit from available_positions to place queen at column 0, update board and bitmasks accordingly.
position = available_positions & (-available_positions)
available_positions &= available_positions - 1
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'Recurse to Row 1 with Updated Bitmasks
Call backtrack for row 1, updating cols, diag1, diag2 to mark attacks from queen at (0,0).
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)Calculate Available Positions at Row 1
Compute available_positions for row 1 considering attacks from previous queen placements.
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))Place Queen at Row 1, Column 2
Select rightmost available bit (column 2) to place queen, update board and bitmasks.
position = available_positions & (-available_positions)
available_positions &= available_positions - 1
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'Recurse to Row 2 with Updated Bitmasks
Call backtrack for row 2 with updated cols, diag1, diag2 reflecting attacks from queens at (0,0) and (1,2).
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)Calculate Available Positions at Row 2
Compute available_positions for row 2 considering attacks from queens placed so far.
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))Place Queen at Row 2, Column 1
Place queen at column 1, update board and bitmasks accordingly.
position = available_positions & (-available_positions)
available_positions &= available_positions - 1
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'Recurse to Row 3 with Updated Bitmasks
Call backtrack for row 3 with updated bitmasks reflecting attacks from queens placed so far.
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)Calculate Available Positions at Row 3
Compute available_positions for row 3 considering attacks from queens placed so far.
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))Place Queen at Row 3, Column 3
Place queen at column 3, update board and bitmasks.
position = available_positions & (-available_positions)
available_positions &= available_positions - 1
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'Record Complete Solution at Row 4
Reached base case row == n, record current board configuration as a valid solution.
if row == n:
solutions.append([''.join(r) for r in board])
returnBacktrack from Row 3, Try Next Position
Remove queen from (3,3) and try next available positions at row 3 (none left), so backtrack further.
board[row][col] = '.'Backtrack from Row 2, Try Next Position
Remove queen from (2,1), try next available positions at row 2 (none left), backtrack to row 1.
board[row][col] = '.'Place Queen at Row 1, Column 3
Try next available position at row 1: column 3, update board and bitmasks.
position = available_positions & (-available_positions)
available_positions &= available_positions - 1
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'Recurse to Row 2 with Updated Bitmasks (Second Branch)
Call backtrack for row 2 with updated bitmasks from queens at (0,0) and (1,3).
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)Calculate Available Positions at Row 2 (Second Branch)
Compute available_positions for row 2 with updated bitmasks.
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2))Place Queen at Row 2, Column 1 (Second Branch)
Place queen at column 1, update board and bitmasks.
position = available_positions & (-available_positions)
available_positions &= available_positions - 1
col = (position & -position).bit_length() - 1
board[row][col] = 'Q'Recurse to Row 3 (Second Branch)
Call backtrack for row 3 with updated bitmasks from queens at (0,0), (1,3), (2,1).
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1)def solveNQueens(n):
solutions = []
board = [['.' for _ in range(n)] for _ in range(n)]
def backtrack(row, cols, diag1, diag2):
if row == n: # STEP 13: base case, record solution
solutions.append([''.join(r) for r in board])
return
available_positions = ((1 << n) - 1) & (~(cols | diag1 | diag2)) # STEP 2,5,8,11,18
while available_positions:
position = available_positions & (-available_positions) # STEP 3,6,9,12,16,19
available_positions &= available_positions - 1 # STEP 3,6,9,12,16,19
col = (position & -position).bit_length() - 1
board[row][col] = 'Q' # STEP 3,6,9,12,16,19
backtrack(row + 1, cols | position, (diag1 | position) << 1, (diag2 | position) >> 1) # STEP 4,7,10,13,17,20
board[row][col] = '.' # STEP 14,15
backtrack(0, 0, 0, 0) # STEP 1
return solutions
if __name__ == '__main__':
print(solveNQueens(4))Key Takeaways
This insight is hard to see from code alone because bitwise operations are compact and non-intuitive without visualization.
Visualizing each recursive call clarifies the systematic search and pruning process.
Understanding when and how solutions are recorded is clearer when watching the recursion unfold step-by-step.
