💡 The recursion explores the last row in this branch.
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))
📊
N-Queens - Watch the Algorithm Execute, Step by Step
Watching each recursive call and bitmask update reveals how the algorithm efficiently prunes invalid queen placements and backtracks to find all solutions.
Step 1/20
·Active fill★Answer cell
enteringrow=0cols=0diag1=0diag2=0
Call Path (current → root)
backtrack(row=0, cols=0, diag1=0, diag2=0)
choosingrow=0cols=0diag1=0diag2=0
Call Path (current → root)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Remaining
0b1111
choosingrow=0cols=0diag1=0diag2=0
Call Path (current → root)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b0001
Remaining
0b1110
Partial: [(0,0)]
enteringrow=1cols=1diag1=2diag2=0
Call Path (current → root)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Partial: [(0,0)]
choosingrow=1cols=1diag1=2diag2=0
Call Path (current → root)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Remaining
0b1100
Partial: [(0,0)]
choosingrow=1cols=1diag1=2diag2=0
Call Path (current → root)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b0100
Remaining
0b1000
Partial: [(0,0), (1,2)]
enteringrow=2cols=5diag1=8diag2=1
Call Path (current → root)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Partial: [(0,0), (1,2)]
choosingrow=2cols=5diag1=8diag2=1
Call Path (current → root)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Remaining
0b0010
Partial: [(0,0), (1,2)]
choosingrow=2cols=5diag1=8diag2=1
Call Path (current → root)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b0010
Partial: [(0,0), (1,2), (2,1)]
enteringrow=3cols=7diag1=18diag2=0
Call Path (current → root)
backtrack(row=3, cols=7, diag1=18, diag2=0)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Partial: [(0,0), (1,2), (2,1)]
choosingrow=3cols=7diag1=18diag2=0
Call Path (current → root)
backtrack(row=3, cols=7, diag1=18, diag2=0)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Remaining
0b1000
Partial: [(0,0), (1,2), (2,1)]
choosingrow=3cols=7diag1=18diag2=0
Call Path (current → root)
backtrack(row=3, cols=7, diag1=18, diag2=0)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b1000
Partial: [(0,0), (1,2), (2,1), (3,3)]
backtrackingrow=4cols=15diag1=36diag2=1
Call Path (current → root)
backtrack(row=4, cols=15, diag1=36, diag2=1)
backtrack(row=3, cols=7, diag1=18, diag2=0)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Partial: [(0,0), (1,2), (2,1), (3,3)]
Found 1: [.Q..,...Q,Q...,..Q.]
backtrackingrow=3cols=7diag1=18diag2=0
Call Path (current → root)
backtrack(row=3, cols=7, diag1=18, diag2=0)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b1000
Partial: [(0,0), (1,2), (2,1)]
Found 1: [.Q..,...Q,Q...,..Q.]
backtrackingrow=2cols=5diag1=8diag2=1
Call Path (current → root)
backtrack(row=2, cols=5, diag1=8, diag2=1)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b0010
Partial: [(0,0), (1,2)]
Found 1: [.Q..,...Q,Q...,..Q.]
choosingrow=1cols=1diag1=2diag2=0
Call Path (current → root)
backtrack(row=1, cols=1, diag1=2, diag2=0)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b01000b1000
Partial: [(0,0), (1,3)]
Found 1: [.Q..,...Q,Q...,..Q.]
enteringrow=2cols=9diag1=20diag2=1
Call Path (current → root)
backtrack(row=1, cols=9, diag1=20, diag2=1)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Partial: [(0,0), (1,3)]
Found 1: [.Q..,...Q,Q...,..Q.]
choosingrow=2cols=9diag1=20diag2=1
Call Path (current → root)
backtrack(row=1, cols=9, diag1=20, diag2=1)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Remaining
0b0010
Partial: [(0,0), (1,3)]
Found 1: [.Q..,...Q,Q...,..Q.]
choosingrow=2cols=9diag1=20diag2=1
Call Path (current → root)
backtrack(row=1, cols=9, diag1=20, diag2=1)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Tried
0b0010
Partial: [(0,0), (1,3), (2,1)]
Found 1: [.Q..,...Q,Q...,..Q.]
enteringrow=3cols=11diag1=44diag2=0
Call Path (current → root)
backtrack(row=3, cols=11, diag1=44, diag2=0)
backtrack(row=2, cols=9, diag1=20, diag2=1)
backtrack(row=1, cols=9, diag1=20, diag2=1)
backtrack(row=0, cols=0, diag1=0, diag2=0)
Partial: [(0,0), (1,3), (2,1)]
Found 1: [.Q..,...Q,Q...,..Q.]
Key Takeaways
✓ Bitmasking efficiently tracks attacked columns and diagonals, enabling fast pruning of invalid queen placements.
This insight is hard to see from code alone because bitwise operations are compact and non-intuitive without visualization.
✓ The recursion tree shows how the algorithm explores each row, tries all available columns, and backtracks when no options remain.
Visualizing each recursive call clarifies the systematic search and pruning process.
✓ Solutions are collected only at the base case when all rows have queens placed, demonstrating how partial answers build up to complete solutions.
Understanding when and how solutions are recorded is clearer when watching the recursion unfold step-by-step.
Practice
(1/5)
1. What is the time complexity of the optimal backtracking solution for the Expression Add Operators problem, where n is the length of the input string? Assume the solution tries all operator insertions and substring splits with pruning and efficient string building.
medium
A. O(4^n) because at each position there are up to 4 choices (3 operators + no operator split)
B. O(n * 3^n) because each digit can be combined with 3 operators and substring splits
C. O(2^n) since only two operators are considered at each step
D. O(n^3) due to substring parsing and operator insertions
Solution
Step 1: Identify branching factor per digit
At each digit, we can insert one of 3 operators (+, -, *) or choose to extend the current operand (no operator), but the main branching factor is 3 operators per split, and substring splits add a factor of n.
Step 2: Calculate total complexity
The total complexity is roughly O(n * 3^n) because for each of the n positions, there are up to 3 operator choices, and substring splits add an n factor.
Final Answer:
Option B -> Option B
Quick Check:
Exponential branching with 3 operators and substring splits [OK]
Hint: 3 operators per digit and substring splits lead to O(n * 3^n) complexity [OK]
Common Mistakes:
Confusing substring parsing cost as cubic
Assuming only 2 operators reduce complexity
Miscounting branching factor as 4
2. The following code attempts to count all N-Queens solutions using bitmask backtracking. Which line contains a subtle bug that causes incorrect counts?
medium
A. Line extracting rightmost 1-bit from available_positions
B. Line updating neg_diags by shifting right after adding position
C. Line updating pos_diags by shifting left after adding position
D. Line computing available_positions with bitwise negation
Solution
Step 1: Understand diagonal bitmask updates
Positive diagonals shift left by 1, negative diagonals shift right by 1 to track attacks on next row.
Step 2: Identify incorrect shift directions
Code incorrectly shifts pos_diags right and neg_diags left, reversing diagonal tracking and causing missed conflicts.
Final Answer:
Option B -> Option B
Quick Check:
Correct diagonal updates are pos_diags << 1 and neg_diags >> 1 [OK]
Hint: Diagonal bitmasks must shift opposite directions each row [OK]
Common Mistakes:
Swapping pos and neg diagonal shifts
Forgetting to shift diagonals at all
3. Suppose the problem changes so that each element in the input array can be used multiple times in each permutation (i.e., unlimited reuse). Which modification to the backtracking algorithm correctly generates all unique permutations with duplicates allowed and reuse permitted?
hard
A. Use backtracking without swapping, but at each recursion level, iterate over all elements and skip duplicates using a 'used' boolean array to avoid repeated elements at the same recursion depth.
B. Remove the 'seen' set and allow swapping elements freely without skipping duplicates.
C. Sort the array and use the same swapping and 'seen' set logic as before, but do not increment the recursion start index to allow reuse.
D. Generate all permutations without sorting or skipping duplicates, then filter duplicates at the end using a hash set.
Solution
Step 1: Understand reuse requirement
Unlimited reuse means elements can be chosen multiple times at each recursion level, so swapping and incrementing start index is not suitable.
Step 2: Identify correct approach
Backtracking without swapping, iterating over all elements at each recursion level, and using a 'used' boolean array to skip duplicates at the same depth ensures unique permutations with reuse.
Step 3: Evaluate other options
Removing 'seen' causes duplicates; swapping without incrementing start index breaks logic; filtering duplicates after generation is inefficient.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking with reuse requires iteration over all elements each recursion, not swapping with start index increment [OK]
Hint: Reuse requires iteration over all elements each recursion, not swapping with start index increment [OK]
Common Mistakes:
Trying to reuse elements by swapping without adjusting recursion index or skipping duplicates properly
4. Suppose the problem is modified so that parentheses can be reused multiple times (e.g., you can insert removed parentheses back anywhere). Which of the following algorithmic changes correctly adapts the BFS approach to handle this variant?
hard
A. Switch to backtracking with memoization to explore insertions and removals explicitly, pruning invalid states
B. Use BFS as is, since it already explores all possible removals and insertions implicitly
C. Use a greedy approach to insert parentheses at earliest invalid positions without BFS or backtracking
D. Modify BFS to allow adding parentheses back by generating new states with insertions at every position
Solution
Step 1: Understand reuse of parentheses
Allowing reuse means we can insert parentheses anywhere, greatly increasing state space and complexity.
Step 2: Analyze BFS suitability
Standard BFS only removes characters; it does not handle insertions, so it cannot explore all states correctly.
Step 3: Identify correct approach
Backtracking with memoization can explicitly explore insertions and removals, pruning invalid states to handle reuse properly.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking with memoization handles insertions and pruning [OK]
Hint: Reuse requires explicit insertion exploration, not just removal BFS [OK]
Common Mistakes:
Assuming BFS handles insertions
Using greedy for complex insertions
Modifying BFS without pruning leads to explosion
5. Suppose the Sudoku solver is modified so that digits can be reused multiple times in the same row, column, or box (i.e., constraints are relaxed). Which of the following statements about the backtracking algorithm is true?
hard
A. Constraint checks can be removed, and backtracking reduces to brute force filling
B. Heuristic ordering of empty cells becomes unnecessary since constraints are relaxed
C. The existing backtracking with constraint propagation still works correctly without changes
D. The problem becomes trivial and can be solved in linear time by filling all empty cells with '1'
Solution
Step 1: Understand effect of relaxing constraints
Allowing repeated digits removes Sudoku constraints, so no need to check row, column, or box validity.
Step 2: Impact on backtracking algorithm
Backtracking degenerates to brute force filling since any digit can be placed anywhere; constraint propagation is useless.
Final Answer:
Option A -> Option A
Quick Check:
Relaxed constraints mean no pruning, so backtracking is brute force [OK]
Hint: Relaxed constraints remove pruning, reverting to brute force [OK]
Common Mistakes:
Assuming heuristics still help
Thinking problem becomes trivial linear time
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.