Jump into concepts and practice - no test required
or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
▶
Steps
setup
Start Backtracking at Position 1 with No Numbers Used
The algorithm begins by calling backtrack at position 1 with an empty set of used numbers (mask = 0). This sets up the initial state for exploring all arrangements.
💡 This initialization matters because it defines the starting point of the recursion tree and the empty partial answer.
Line:backtrack(1, 0)
💡 The recursion tree root corresponds to the first position with no numbers chosen yet.
compare
Try Number 1 at Position 1
Check if number 1 can be placed at position 1. Since 1 is unused and 1 divides 1, the condition holds true.
💡 Checking divisibility and usage ensures only valid numbers are chosen for the current position.
Line:if (used & bit) == 0 and (num % pos == 0 or pos % num == 0):
💡 The algorithm filters candidates by usage and divisibility before recursing.
traverse
Recurse with Number 1 Used at Position 1
Number 1 is chosen for position 1, so the bitmask is updated to mark 1 as used (used = 1). The algorithm recurses to position 2.
💡 Updating the bitmask tracks which numbers are used, preventing reuse in the same arrangement.
Line:backtrack(pos + 1, used | bit)
💡 Recursion advances to the next position with updated state reflecting the choice made.
compare
Try Number 1 at Position 2 (Already Used)
Check if number 1 can be placed at position 2. The bitmask shows 1 is already used, so this choice is skipped.
💡 Skipping used numbers avoids invalid arrangements with repeated elements.
Line:if (used & bit) == 0 ...
💡 The bitmask efficiently prevents reuse without extra data structures.
compare
Try Number 2 at Position 2
Number 2 is unused and satisfies the divisibility condition (2 divides 2), so it is a valid choice for position 2.
💡 Checking divisibility ensures the arrangement meets problem constraints.
Line:if (used & bit) == 0 and (num % pos == 0 or pos % num == 0):
💡 The algorithm confirms valid candidates before recursing deeper.
traverse
Recurse with Number 2 Used at Position 2
Number 2 is chosen for position 2, updating the bitmask to used=3 (binary 11). The algorithm recurses to position 3, which is beyond n.
💡 Reaching position > n means a complete valid arrangement is found.
Line:backtrack(pos + 1, used | bit)
💡 The recursion advances to a leaf node indicating a valid solution.
reconstruct
Found a Complete Valid Arrangement [1, 2]
Since position 3 is beyond n, increment the count of valid arrangements by 1.
💡 Counting valid arrangements is the goal of the algorithm.
Line:if pos > n:
count += 1
return
💡 Leaf nodes correspond to complete valid solutions contributing to the final count.
backtrack
Backtrack to Position 2 to Try Next Number
Backtrack from position 3 to position 2 to try other unused numbers for position 2.
💡 Backtracking allows exploring alternative choices to find all valid arrangements.
Line:return (implicit backtrack)
💡 Backtracking restores state to explore other branches in the recursion tree.
backtrack
Backtrack to Position 1 to Try Number 2
All numbers tried at position 2 with number 1 at position 1, so backtrack to position 1 to try number 2.
💡 Backtracking to previous positions enables exploring different partial answers.
Line:return (implicit backtrack)
💡 Backtracking moves up the recursion tree to try unexplored branches.
compare
Try Number 2 at Position 1
Number 2 is unused and satisfies divisibility condition (2 divides 1 is false, but 1 divides 2 is true), so it is a valid choice.
💡 Divisibility condition is symmetric, allowing multiple valid choices.
Line:if (used & bit) == 0 and (num % pos == 0 or pos % num == 0):
💡 The algorithm tries all valid numbers at each position to find all solutions.
traverse
Recurse with Number 2 Used at Position 1
Number 2 is chosen for position 1, updating used to 2 (binary 10). The algorithm recurses to position 2.
💡 State updates reflect the current partial answer and prevent reuse.
Line:backtrack(pos + 1, used | bit)
💡 Recursion continues building the arrangement from the new partial answer.
compare
Try Number 1 at Position 2
Number 1 is unused and satisfies divisibility condition (1 divides 2), so it is valid for position 2.
💡 Checking divisibility ensures only valid numbers are chosen.
Line:if (used & bit) == 0 and (num % pos == 0 or pos % num == 0):
💡 The algorithm finds another valid candidate for position 2.
traverse
Recurse with Number 1 Used at Position 2
Number 1 is chosen for position 2, updating used to 3 (binary 11). The algorithm recurses to position 3, beyond n.
💡 Reaching beyond n signals a complete valid arrangement.
Line:backtrack(pos + 1, used | bit)
💡 The recursion reaches a leaf node representing a valid solution.
reconstruct
Found a Complete Valid Arrangement [2, 1]
Position 3 is beyond n, so increment count of valid arrangements by 1.
💡 Counting valid arrangements is the algorithm's goal.
Line:if pos > n:
count += 1
return
💡 Leaf nodes correspond to complete valid solutions contributing to the final count.
backtrack
Backtrack to Position 2 to Try Other Numbers
Backtrack from position 3 to 2 to try other numbers, but all have been tried, so backtrack further.
💡 Backtracking explores all branches to find all solutions.
Line:return (implicit backtrack)
💡 Backtracking restores previous states to explore unexplored branches.
backtrack
Backtrack to Position 1 to Try Other Numbers
Backtrack from position 2 to 1; all numbers tried at position 1, so recursion ends.
💡 Backtracking completes when all branches are explored.
Line:return (implicit backtrack)
💡 The recursion tree traversal is complete with all solutions found.
reconstruct
Return Final Count of Valid Arrangements
All recursive calls complete; the algorithm returns the total count of valid arrangements found: 2.
💡 Returning the final count concludes the algorithm.
Line:return count
💡 The final answer is the total number of valid beautiful arrangements.
def countArrangement(n: int) -> int:
count = 0
def backtrack(pos, used): # STEP 1
nonlocal count
if pos > n: # STEP 7,14
count += 1
return
for num in range(1, n + 1): # STEP 2,4,5,10,12
bit = 1 << (num - 1) # STEP 2
if (used & bit) == 0 and (num % pos == 0 or pos % num == 0): # STEP 2,5,10,12
backtrack(pos + 1, used | bit) # STEP 3,6,11,13
backtrack(1, 0) # STEP 1
return count # STEP 17
if __name__ == '__main__':
print(countArrangement(2)) # Output: 2
📊
Beautiful Arrangement - Watch the Algorithm Execute, Step by Step
Watching this step-by-step recursion tree is the fastest way to understand how backtracking explores all valid permutations and prunes invalid ones efficiently.
Step 1/17
·Active fill★Answer cell
enteringpos=1used=0
Call Path (current → root)
backtrack(pos=1, used=0)
Remaining
num=1num=2
choosingpos=1used=0
Call Path (current → root)
backtrack(pos=1, used=0)
Remaining
num=1num=2
enteringpos=2used=1
Call Path (current → root)
backtrack(pos=2, used=1)
backtrack(pos=1, used=0)
Remaining
num=1num=2
Partial: [1]
choosingpos=2used=1
Call Path (current → root)
backtrack(pos=2, used=1)
backtrack(pos=1, used=0)
Tried
num=1
Remaining
num=2
Partial: [1]
choosingpos=2used=1
Call Path (current → root)
backtrack(pos=2, used=1)
backtrack(pos=1, used=0)
Tried
num=1
Remaining
num=2
Partial: [1]
enteringpos=3used=3
Call Path (current → root)
backtrack(pos=3, used=3)
backtrack(pos=2, used=1)
backtrack(pos=1, used=0)
Partial: [1, 2]
backtrackingpos=3used=3
Call Path (current → root)
backtrack(pos=3, used=3)
backtrack(pos=2, used=1)
backtrack(pos=1, used=0)
Partial: [1, 2]
Found 1: [1,2]
choosingpos=2used=1
Call Path (current → root)
backtrack(pos=2, used=1)
backtrack(pos=1, used=0)
Tried
num=1num=2
Partial: [1]
Found 1: [1,2]
choosingpos=1used=0
Call Path (current → root)
backtrack(pos=1, used=0)
Tried
num=1
Remaining
num=2
Found 1: [1,2]
choosingpos=1used=0
Call Path (current → root)
backtrack(pos=1, used=0)
Tried
num=1
Remaining
num=2
Found 1: [1,2]
enteringpos=2used=2
Call Path (current → root)
backtrack(pos=2, used=2)
backtrack(pos=1, used=0)
Remaining
num=1num=2
Partial: [2]
Found 1: [1,2]
choosingpos=2used=2
Call Path (current → root)
backtrack(pos=2, used=2)
backtrack(pos=1, used=0)
Remaining
num=1num=2
Partial: [2]
Found 1: [1,2]
enteringpos=3used=3
Call Path (current → root)
backtrack(pos=3, used=3)
backtrack(pos=2, used=2)
backtrack(pos=1, used=0)
Partial: [2, 1]
Found 2: [1,2] [2,1]
backtrackingpos=3used=3
Call Path (current → root)
backtrack(pos=3, used=3)
backtrack(pos=2, used=2)
backtrack(pos=1, used=0)
Partial: [2, 1]
Found 2: [1,2] [2,1]
backtrackingpos=2used=2
Call Path (current → root)
backtrack(pos=2, used=2)
backtrack(pos=1, used=0)
Tried
num=1num=2
Partial: [2]
Found 2: [1,2] [2,1]
backtrackingpos=1used=0
Call Path (current → root)
backtrack(pos=1, used=0)
Tried
num=1num=2
Found 2: [1,2] [2,1]
Answer: 2
Total calls: 7 · Pruned: 0
Key Takeaways
✓ Backtracking explores all permutations by incrementally building partial answers and pruning invalid choices early.
This insight is hard to see from code alone because the recursion and pruning happen implicitly and can be confusing without visualization.
✓ Using a bitmask to track used numbers is an efficient way to avoid extra data structures and quickly check usage.
Understanding bitmask operations is easier when you see how they correspond to used/unset states in the recursion tree.
✓ The divisibility condition filters candidates at each position, shaping the recursion tree's branching and pruning.
Seeing which branches are pruned and which are explored concretely demonstrates the problem constraints in action.
Practice
(1/5)
1. Given the following code snippet for getPermutation, what is the returned value when calling getPermutation(3, 3)?
easy
A. '123'
B. '312'
C. '213'
D. '231'
Solution
Step 1: Trace factorial array computation
factorial = [1, 1, 2] for n=3.
Step 2: Calculate indices and build result
k=3 -> k=2 zero-based. For i=2: idx=2//2=1, pick '2'. For i=1: k=2%2=0, idx=0//1=0, pick '1'. For i=0: k=0%1=0, idx=0//1=0, pick '3'. Result = '213'.
Hint: Remember to convert k to zero-based before indexing [OK]
Common Mistakes:
Off-by-one error in k adjustment
Wrong index calculation in loop
Not popping used numbers
2. Consider the following code snippet implementing the optimal backtracking solution for Word Search. Given the board = [["A","B","C"],["D","E","F"],["G","H","I"]] and word = "BEF", what is the return value of exist(board, word)?
easy
A. True
B. False
C. Raises IndexError
D. Infinite recursion
Solution
Step 1: Trace dfs starting at cell (0,1) 'B'
Word starts with 'B', found at (0,1). dfs(0,1,0) matches 'B'.
Step 2: Explore neighbors for next chars 'E' and 'F'
From (0,1), neighbors checked: (1,1) 'E' matches next char, then (1,2) 'F' matches last char. All matched, return True.
Final Answer:
Option A -> Option A
Quick Check:
All characters found sequentially with valid moves [OK]
Hint: Trace dfs calls carefully to confirm path exists [OK]
Common Mistakes:
Assuming no path due to adjacency confusion
Missing in-place marking effect
3. The following code attempts to generate all permutations of a list using Heap's algorithm but contains a subtle bug:
def permute(nums):
res = [nums[:]]
c = [0] * len(nums)
i = 0
while i < len(nums):
if c[i] < i:
if i % 2 == 0:
nums[0], nums[i] = nums[i], nums[0]
else:
nums[c[i]], nums[i] = nums[i], nums[c[i]]
res.append(nums)
c[i] += 1
i = 0
else:
c[i] = 0
i += 1
return res
What is the bug in this code?
medium
A. The used array c is not reset properly, causing infinite loop
B. Appending nums directly without copying causes all entries in res to reference the same list
C. Swapping nums[0] and nums[i] when i is even is incorrect logic
D. The initial res list should be empty, not contain nums[:] at start
Solution
Step 1: Identify how results are stored
The code appends nums directly to res without copying.
Step 2: Understand consequences of appending references
Since nums is mutated in-place, all entries in res point to the same list, resulting in duplicates of the final permutation.
Final Answer:
Option B -> Option B
Quick Check:
Appending a copy nums[:] fixes the bug [OK]
Hint: Always append a copy of mutable lists to results [OK]
Common Mistakes:
Forgetting to copy before appending results in duplicate references
4. The following BFS code attempts to remove invalid parentheses but contains a subtle bug:
from collections import deque
def is_valid(s: str) -> bool:
count = 0
for c in s:
if c == '(': count += 1
elif c == ')':
count -= 1
if count < 0:
return False
return count == 0
def remove_invalid_parentheses_bfs(s: str):
res = []
visited = set([s])
queue = deque([s])
found = False
while queue:
size = len(queue)
for _ in range(size):
curr = queue.popleft()
if is_valid(curr):
res.append(curr)
found = True
if found:
continue
for i in range(len(curr)):
# BUG: removing characters other than parentheses
next_str = curr[:i] + curr[i+1:]
if next_str not in visited:
visited.add(next_str)
queue.append(next_str)
return res
What is the bug in this code?
medium
A. The found flag is reset inside the inner loop, causing premature termination
B. The is_valid function incorrectly returns True for unbalanced strings
C. The visited set is not updated correctly, causing duplicates
D. The code removes characters even if they are not '(' or ')', expanding search unnecessarily
Solution
Step 1: Identify character removal condition
The code removes characters at every index without checking if they are '(' or ')', which is incorrect.
Step 2: Consequence of bug
Removing non-parenthesis characters leads to invalid strings and unnecessary BFS states, increasing complexity and possibly missing valid results.
Final Answer:
Option D -> Option D
Quick Check:
Only parentheses should be removed to prune search space [OK]
Hint: Only remove '(' or ')' characters during BFS [OK]
Common Mistakes:
Removing all characters
Incorrect visited updates
Misusing found flag
5. Suppose the N-Queens problem is modified so that queens can be placed multiple times in the same column (i.e., column conflicts are ignored), but diagonal conflicts still apply. Which modification to the bitmask backtracking approach correctly counts all valid solutions under this relaxed constraint?
hard
A. Track only column bitmask and ignore diagonal bitmasks
B. Keep all bitmasks but allow queens to be placed in any column regardless of conflicts
C. Remove the column bitmask and only track diagonal bitmasks for conflicts
D. Use a greedy approach placing queens in the first available diagonal position per row
Solution
Step 1: Analyze relaxed constraints
Column conflicts are ignored, so column bitmask is unnecessary; diagonal conflicts remain.
Step 2: Modify bitmask tracking accordingly
Remove column bitmask from conflict checks and recursive calls; keep positive and negative diagonal bitmasks to prune invalid placements.