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. Consider the following code snippet for the Word Search II problem using backtracking with a Trie. Given the board and words below, what is the content of the result list after the function completes?
Board:
[['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']]
Words: ["oath", "pea", "eat", "rain"]
easy
A. ["eat", "rain"]
B. ["pea", "rain"]
C. ["oath", "pea", "eat", "rain"]
D. ["oath", "eat"]
Solution
Step 1: Trace backtracking for "oath" and "eat"
Starting from 'o' at (0,0), the path "o-a-t-h" exists, so "oath" is found. Similarly, "eat" is found starting at 'e' (1,0) following adjacency.
Step 2: Verify "pea" and "rain" are not found
"pea" and "rain" cannot be formed due to missing adjacency or letters in the board.
Final Answer:
Option D -> Option D
Quick Check:
Output matches expected found words [OK]
Hint: Only words with valid adjacency and prefix in Trie appear [OK]
Common Mistakes:
Assuming all words appear regardless of adjacency.
2. You are given a 2D grid of characters and a target string. You need to determine if the string can be formed by sequentially adjacent cells (horizontally or vertically) without revisiting any cell. Which algorithmic approach best guarantees an optimal solution for this problem?
easy
A. Backtracking with in-place marking and exploring all four directions
B. Dynamic Programming with memoization to store partial matches
C. Greedy search by always choosing the next matching character closest to the start
D. Breadth-first search (BFS) to explore all possible paths simultaneously
Solution
Step 1: Understand problem constraints
The problem requires checking if a word can be formed by adjacent cells without reuse, which is a classic backtracking scenario.
Step 2: Identify suitable algorithm
Backtracking with in-place marking efficiently explores all paths while preventing revisiting cells, guaranteeing correctness and optimal pruning.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking explores all valid paths with pruning [OK]
Hint: Backtracking fits adjacency and no-reuse constraints [OK]
Common Mistakes:
Assuming greedy or DP can solve adjacency constraints optimally
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. 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 Word Search problem is modified so that the same cell can be used multiple times in forming the word. Which modification to the backtracking algorithm correctly handles this change?
hard
A. Add a global visited set to track cells used in all paths
B. Keep marking visited cells but reset them after each recursion
C. Remove the in-place marking of visited cells to allow reuse
D. Use dynamic programming to store partial results and avoid recomputation
Solution
Step 1: Understand new constraint
Cells can be reused multiple times, so marking visited cells to prevent reuse is no longer needed.