Backtracking : A Comprehensive Guide
Published:
Backtracking is a powerful algorithmic technique for solving problems related to permutations, combinations, and subsets. The essence of backtracking lies in exploring all potential solutions and then backtracking by undoing the last decision and trying the next possibility. Below is a generalized template for backtracking that can be adapted to a variety of problems like subsets, permutations, and combinations.
void backtrack(vector<int>& nums, int start, vector<int>& curr, vector<vector<int>>& res) {
// Base case: When to add `curr` to `res`
if (/* condition to add curr to res */) {
res.push_back(curr); // condition when we should stop our exploration
return;
}
// Recursive case: Try each possibility from the current start position
for (int i = start; i < nums.size(); i++) {
// Make a choice by adding nums[i] to curr
curr.push_back(nums[i]); // explore candidate
// Recur with updated start position
backtrack(nums, i + 1, curr, res);
// Undo the choice (backtrack)
curr.pop_back(); // abandon candidate
}
}
A more generalized pattern for backtracking problems looks like the code below:
void backtrack(arguments) {
if (condition == true) { // Condition when we should stop our exploration.
result.push_back(current);
return;
}
for (int i = num; i <= last; i++) {
current.push_back(i); // Explore candidate.
backtrack(arguments);
current.pop_back(); // Abandon candidate.
}
}
Template Explanation:
Base Case:
- This is where you define when to add the current state
curr
to the resultres
. For example, in permutations, it would be whencurr
reaches the same length asnums
. For combinations, it’s whencurr
contains exactlyk
elements.
- This is where you define when to add the current state
Recursive Case:
- This involves iterating over the choices (usually starting from the current index to avoid revisiting previous elements). The recursive call is made after making a choice, and then the choice is undone to explore other possibilities.
Types of Problems:
Once you solve a certain number of backtracking-related questions, you’ll start noticing that most backtracking problems are related to either one of these three parent problems → permutations, combinations, or subsets. (All of them are easily solvable using the generalized backtracking template).
- Permutation: Can be thought of as the number of ways to order some input.
- Example: Permutations of ABCD, taken 3 at a time (24 variants): ABC, ACB, BAC, BCA, …
- Combination: Can be thought of as the number of ways of selecting from some input.
- Example: Combination of ABCD, taken 3 at a time (4 variants): ABC, ABD, ACD, and BCD.
- Subset: Can be thought of as a selection of objects from the original set.
- Example: Subset of ABCD: ‘A’, ‘B’, ‘C’, ‘D,’ ‘A,B’, ‘A,C’, ‘A,D’, ‘B,C’, ‘B,D’, ‘C,D’, ‘A,B,C’, …
Explanation of popular problems like finding permutations, combinations, and subsets is included below:
Specializations for Different Problems
The core logic to find permutations, combinations, and subsets:
If you examine the core logic behind solving permutations, combinations, and subsets, you’ll notice a significant similarity in the approach to these problems. This is why backtracking serves as an effective algorithmic paradigm/pattern for tackling various types of related challenges.
For Subsets:
Generating all subsets of a given set of numbers is a fundamental problem in computer science, often appearing in coding interviews.
Problem: If we’re given an array of distinct integers nums = [1,2,3]
, the subsets of this array are [[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]]
.
The backtracking technique is an effective method to solve this problem by recursively building each subset. We incrementally construct subsets by either including or excluding each element in the array, exploring all possible combinations.
void backtrack(vector<int>& nums, int start, vector<int>& curr, vector<vector<int>>& res) {
res.push_back(curr); // Add the current subset to the result
for (int i = start; i < nums.size(); i++) {
curr.push_back(nums[i]);
backtrack(nums, i + 1, curr, res);
curr.pop_back(); // Backtrack
}
}
For Permutations
Problem: If we’re given an array of distinct integers nums = [1,2,3]
, permutations of this array are [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
.
The backtracking technique allows us to solve this problem recursively by trying to build a solution incrementally. We build permutations by swapping elements in the list and exploring all distinct possibilities.
void backtrack(vector<int>& nums, int start, vector<vector<int>>& res) {
if (start >= nums.size()) {
res.push_back(nums);
return;
}
for (int i = start; i < nums.size(); i++) {
swap(nums[start], nums[i]); // Swap to create a new permutation
backtrack(nums, start + 1, res);
swap(nums[start], nums[i]); // Backtrack
}
}
For Combinations:
Problem: If we’re given an array of distinct integers nums = [1,2,3]
and asked to find all combinations of length 2
, the combinations of this array are [[1,2], [1,3], [2,3]]
.
The backtracking approach allows us to solve this problem by building combinations incrementally. We construct combinations by choosing elements in sequence, ensuring that each combination is unique and all possibilities are explored.
void backtrack(int n, int k, int start, vector<int>& curr, vector<vector<int>>& res) {
if (curr.size() == k) {
res.push_back(curr);
return;
}
for (int i = start; i <= n; i++) {
curr.push_back(i);
backtrack(n, k, i + 1, curr, res);
curr.pop_back(); // Backtrack
}
}
Selected Backtracking Problems
The following exhaustive list of problems are selected from the top 100 liked problems, most frequently asked problems, and LeetCode 75 lists. The list below will be expanded iteratively to include new questions. This is ** THE ONLY** list of problems that you’ll ever need to master backtracking and make it one of your strongest subjects. Only high quality problems will be added to the list below:
- Generate Parentheses
- N-Queens
- Letter Combinations of a Phone Number
- Word Break
- Word Break II
- Subsets
- Subsets II
- Word Search
- Word Search II
- Combination Sum
- Combination Sum II
- Combination Sum III
- Sudoku Solver
- Path With Maximum Gold
- Remove Invalid Parentheses
- Permutations
- Permutations II
- Combinations
- Target Sum
- N-Queens II
- Palindrome Partitioning
- Restore IP Addresses
- Partition to K Equal Sum Subsets
- Matchsticks to Square
- Splitting a String Into Descending Consecutive Values
- Find Unique Binary String
- Maximum Length of a Concatenated String with Unique Characters