Permutations are selections where order matters: [1, 2] and [2, 1] are different answers. Subsets and combinations only look forward from a start index. Here every position may take any element not used yet. The tree has N choices at the first level, N − 1 at the next, and so on, which gives N! leaves.
The idea#
Build the answer one position at a time:
- Choices: every element not already in the path, so the loop always runs over all n indices with a
usedcheck. - State: the
pathso far and ausedarray (or set). - Goal: the path has n elements; snapshot a copy.
Why it works: each level fixes one position, and the used check guarantees every element appears exactly once. Every ordering is one root-to-leaf path, so none is missed and none repeats (when the values are distinct).
The cost follows from the size of that tree. There are leaves and each copy costs , so time is . The recursion is N deep and the used array is size N, so the extra space is , not counting the output.
Slicing vs a used array#
You need some way to track which elements are still free, and the choice affects memory. A tempting version passes the remaining elements down by slicing: find(path + [x], rest[:i] + rest[i + 1:]). It's correct, but each level makes new lists, and all N frames are alive at once: N + (N − 1) + … + 1 = extra memory. The used array with append and pop keeps it at .
Duplicate values#
When the input has repeated values, two equal elements produce the same orderings twice. The fix is to sort first, so equal values sit next to each other, and then force equal values into a fixed order. Working out the exact skip condition is part of Permutations II.
How to spot it#
These cues point to permutations.
- "All permutations", "all arrangements", "all orderings".
- Each position must take a different element.
- Constraints such as N ≤ 8 to 12, where N! is still affordable.
- A permutation with rules about where each element may go (divisibility, adjacency, attacking queens).
Template#
The template fills one position per call by trying every unused element there. It also has a slot for placement rules, explained right after it.
track():
if length of path == n:
record a copy of path
return
for i from 0 to n - 1: # every position sees every element
if used[i]: continue
if placing items[i] here breaks a rule: continue # prune early
mark i used; append items[i] to path
track()
pop from path; unmark i
track()
Check placement rules in the loop, before you place the value, not at the leaf. A bad prefix is then cut immediately instead of being completed in every way and then rejected.
Pitfalls#
Watch for these when moving from combinations to permutations.
- Looping from
start. That generates combinations, not permutations; permutations always loop from 0. - Forgetting to reset
used[i]after the recursive call. - Deduplicating with
path not in res. It works, but each check scans every result so far. Sort and skip siblings instead. - Under-counting memory with slicing. It's auxiliary, not .
Problems in this pattern#
These problems use this pattern. Solve them before you open the hints below. Start with Permutations.
Hints#
Open a hint only after you have tried the problem for a while.
Hint: Permutations
The values are distinct, so the template works with no placement rule. Remember to record a copy (path[:]), not the live list.
Hint: Permutations II
Sort first. Skip nums[i] when i > 0, nums[i] == nums[i - 1] and used[i - 1] is False. That forces equal values to be used left to right: the second 1 may only follow the first, never stand in for it, so each distinct ordering appears once.
Hint: Permutations II: slicing style
If you pass sliced remaining lists instead, sort first and skip rest[i] == rest[i - 1] for i > 0. This works because removing one element from a sorted list leaves it sorted, so equal values stay neighbors at every level.
Hint: Beautiful Arrangement
Position p (1-indexed) may take value v only if p % v == 0 or v % p == 0. Check this before you place the value, so bad prefixes die early.
Hint: N-Queens
One queen per row means a solution is a permutation of the columns with extra diagonal rules. The constraint sets that make each check are covered in Grid & Constraint Boards.
Key takeaways#
- Order matters, so every position loops over all elements, filtered by
used. - Time is ; extra space is with a
usedarray, with slicing. - Duplicates: sort, then force equal values into a fixed order.
- Check placement rules before recursing, not at the leaf.