Backtracking: Code Templates for Permutations and Subsets
If you have not read the concept guide yet, start there for the intuition and complexity analysis. This page gives you the code you can memorize and adapt during an interview.
1. The Generic Backtracking Template
This is the skeleton of almost every backtracking algorithm. It follows a recursive pattern of making a choice, exploring its consequences, and then undoing that choice.
function backtrack(result, current, data) {
// 1. Base case: Is the current solution complete?
if (isValid(current)) {
result.push([...current]); // Add a copy
return;
}
// 2. Iterate through available candidates
for (let candidate of getCandidates(data, current)) {
if (isPossible(candidate, current)) {
// 3. Make a choice (State Change)
current.push(candidate);
// 4. Recurse to explore this branch
backtrack(result, current, data);
// 5. Backtrack (Undo State Change)
current.pop();
}
}
}Code Breakdown
The template relies on three main markers:
- State: Usually represented by
current, the path taken so far. - Choices: The for loop that explores all possible next steps.
- Constraints: The
isValidcheck that decides when to stop.
graph LR
A["State: []"] --> B[Choice: 1]
B --> C["State: [1]"]
C --> D[Explore Sub-trees]
D -- "Backtrack" --> E["State: []"]
E --> F[Choice: 2]
F --> G["State: [2]"]
2. Common Variations
Most interview problems are specific versions of this generic template.
A. Permutations
Used when you need all possible orderings. The key addition is a used array that tracks which elements have already been placed, since permutations use every element exactly once. See Problem 1: Permutations
.
Visual Logic:
graph TD
Root["[]"] --> A["[1]"]
Root --> B["[2]"]
A --> AB["[1,2]"]
A --> AC["[1,3]"]
AB -- "Backtrack" --> A
AC -- "Backtrack" --> A
function permute(nums) {
const result = [];
const used = new Array(nums.length).fill(false);
function backtrack(current) {
if (current.length === nums.length) {
result.push([...current]);
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue;
used[i] = true;
current.push(nums[i]);
backtrack(current);
current.pop();
used[i] = false;
}
}
backtrack([]);
return result;
}B. Subsets
Used when you need every possible combination regardless of order. The start index prevents duplicate sets by only looking forward. See Problem 2: Subsets
.
Visual Logic:
graph TD
Root["[]"] --> S1["[1]"]
S1 --> S12["[1,2]"]
S12 --> S123["[1,2,3]"]
Root --> S2["[2]"]
S2 --> S23["[2,3]"]
Root --> S3["[3]"]
function subsets(nums) {
const result = [];
function backtrack(start, current) {
result.push([...current]);
for (let i = start; i < nums.length; i++) {
current.push(nums[i]);
backtrack(i + 1, current);
current.pop();
}
}
backtrack(0, []);
return result;
}Now head to the practice problems to apply these templates to real interview questions.