💡 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. You are given a string containing only digits. Your task is to split it into exactly four parts such that each part represents a valid IP address segment (0 to 255, no leading zeros except for '0'). Which algorithmic approach guarantees finding all valid IP addresses efficiently?
easy
A. Sorting the digits and then partitioning into four segments
B. Greedy approach that picks the shortest valid segment at each step
C. Dynamic programming to count the number of valid segmentations
D. Backtracking with pruning to explore all valid segmentations
Solution
Step 1: Understand the problem constraints
The problem requires enumerating all valid IP addresses, which involves exploring multiple segmentations of the string.
Step 2: Evaluate algorithm suitability
Greedy approaches fail because they may miss valid segmentations; DP counting does not generate all solutions; sorting digits destroys original order. Backtracking with pruning systematically explores all valid partitions.
Final Answer:
Option D -> Option D
Quick Check:
Backtracking explores all partitions with pruning to avoid invalid segments [OK]
Hint: Backtracking explores all segmentations with pruning [OK]
Common Mistakes:
Assuming greedy can find all valid IPs
Using DP counting without generating solutions
2. What is the time complexity of the backtracking with bitmask optimization approach for counting beautiful arrangements of size n?
medium
A. O(n * n!)
B. O(n^2)
C. O(n * 2^n)
D. O(n!)
Solution
Step 1: Identify the search space
The algorithm explores permutations of n numbers, which is n! in worst case.
Step 2: Consider pruning and bitmask usage
Bitmask helps track used numbers efficiently, pruning invalid branches but the total number of recursive calls is proportional to n * n! because for each position, up to n choices are tried.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking explores permutations with n choices per position, leading to O(n * n!) complexity [OK]
Hint: Backtracking permutations with bitmask -> O(n * n!) time [OK]
Common Mistakes:
Confusing bitmask with exponential 2^n complexity
Assuming polynomial due to pruning
3. What is the time complexity of the optimal palindrome partitioning algorithm that uses backtracking with dynamic palindrome checks by expanding around centers for palindrome verification on a string of length n?
medium
A. O(n^2) because palindrome checks are done in constant time using DP
B. O(n^3) due to checking all substrings and palindrome verification
C. O(2^n) since all partitions are generated without extra palindrome checks
D. O(n * 2^n) because each character can start a partition and palindrome checks are O(n)
Solution
Step 1: Identify number of partitions
There are up to 2^(n-1) ways to partition a string of length n.
Step 2: Analyze palindrome check cost
Using expand around center, palindrome check per substring is O(n) worst case, done during backtracking.
Final Answer:
Option D -> Option D
Quick Check:
Combining partitions and palindrome checks yields O(n * 2^n) [OK]
Hint: Partitions grow exponentially; palindrome checks add linear factor [OK]
Common Mistakes:
Assuming palindrome checks are O(1) without DP
Confusing total partitions with time complexity
Ignoring recursion stack space
4. What is the time complexity of the BFS approach for removing invalid parentheses from a string of length n, considering the worst case?
medium
A. O(n * 2^n) because there can be up to 2^n states and each validity check takes O(n)
B. O(n!) because permutations of removals are explored
C. O(2^n) because BFS explores all subsets of characters without repeated checks
D. O(n^2) because each level removes one character and checks validity in O(n)
Solution
Step 1: Count possible states
Each character can be either removed or kept, so up to 2^n possible strings can be generated.
Step 2: Analyze per-state cost
Each string requires an O(n) validity check, so total worst-case time is O(n * 2^n).
Final Answer:
Option A -> Option A
Quick Check:
2^n states times O(n) validity checks [OK]
Hint: 2^n states times O(n) validity checks [OK]
Common Mistakes:
Confusing O(n^2) with BFS complexity
Ignoring validity check cost
Assuming factorial complexity
5. Examine the following code snippet for the Word Search II problem. Which line contains a subtle bug that can cause incorrect results or infinite loops?
medium
A. Line: currNode = node.children.get(letter)
B. Line: Missing marking of board[r][c] as visited before recursion
C. Line: if currNode.word:
D. Line: board[r][c] = letter at the end
Solution
Step 1: Identify visited marking necessity
Backtracking requires marking the current cell as visited (e.g., replacing letter with '#') to avoid revisiting the same cell in the current path.
Step 2: Locate missing visited marking
The code lacks the line that marks board[r][c] as visited before recursive calls, causing revisits and potential infinite loops.
Final Answer:
Option B -> Option B
Quick Check:
Without visited marking, recursion revisits same cells [OK]
Hint: Visited marking prevents revisiting same cell in recursion [OK]
Common Mistakes:
Forgetting to mark visited cells or unmark after recursion.