Backtracking
Notes and typical questions for backtracking algorithm
Notes
General code template
private void backtracking(parameters) {
if (termination condition) {
collect result;
return;
}
for (choose items to be processed in the same level) {
process items;
backtracking(parameters);
restore status;
}
}Permutation problems and Combination problems are very similar, differences are
Combination use
startIndexto dedup in branch direction. Permutation useused[]/Setto dedup in branch direction. Reason: [1,2] and [2,1] are the same results in combination but different results in permutation. Therefore, in each level,forloop starts fromstartIndexin Combination but from0(skip items inused[]) in permutation.
Typical Questions
Combination
Notes
Pick
kor any number of items fromnitems w/o special requirement. The order of results does NOT matterHandle pruning logic
Does not satisfy the requirement: In the 1st step of each level or the
forloop if the input is sortedSet end condition for
forloop based onkvalue ifkis a constant
Handle dedup logic if necessary
Termination condition and
return;is not required for some cases becauseforloop itself can cover the termination condition. But I think it's still good to have it to make logic clear.Subset problem
It is a variation of combination problems. The key difference is that the subset problem needs to collect results in every node but the collection problem only collects results in the leaf node. Subset problem = classic collection problem under this assumption
No requirement for
kNo other requirement for the result (e.g. sum=target...)
It has another way of implementation. Pick each item or not in each level without using
forloop in each level.
🧩 Unique input items & Pick each item once & k is a constant
k is a constantNotes
startIndex: Inforloop of each level, Pass the currenti+1asstartIndexfor the next level so that each item can be picked only once.Prune
Set end condition for
forloop based onkvalueIf necessary, do
return;at the 1st step of each level if it already does not satisfy the requirement
Time complexity: O(nk) (This is just the number of all combinations). k is the number of elements we need to collect. n is the length of input
candidates. Theoretically, it should be O(C(n,k)). We are taking the upper limit(i.e., Big O) for easier calculation.
🌟 ⚪ LC 77. Combinations
Make sure to prune during backtracking
Optimal Answer. TC: O(k∗C(n,k)), SC: O(k)
⚪ LC 216. Combination Sum III
Similar to the above one. Do pruning for 2 cases (selection & sum).
Do NOT introduce any useless argument! For example, we can minus the sum in each level instead of having both
sumandtargetarguments. We can usepath.size()to check the termination condition instead of using anotherindexargument.Optimal Answer. TC: O(k∗C(9,k)), SC: O(k)
⚪ LC 17. Letter Combinations of a Phone Number
Use
String[]to replaceMapto optimize space a bit.Optimal Answer. n is the number of digits. TC: O(4n∗n), SC: O(n)
🧩 Unique input items & Pick each item multiple times & No requirement for k
kNotes
startIndex: Inforloop of each level, Pass the currentiasstartIndexfor the next level so that each item can be picked up multiple times.Prune
Without sorting: Do this logic in the first step of each level
With sorting (Better): Put this logic into
forloop to skip all other items within the same level if it already does not satisfy the requirement.
🌟 🟠 LC 39. Combination Sum
Approach 1: Answer from Laioffer.
Approach 2: Answer.
The solution can be further optimized by performing a second pruning, but the input needs to be sorted first.
TC: O((t/m)∗nt/m+1). n is the length of the
candidates; t is the target sum; m is the min value in thecandidates. At each step, we can select any number any number of times. The maximum depth of the recursion tree would be t/m. At each level, we have n choices at max. At the end of each combination, we need to copypathintoresult.SC: O(t/m) for additional memory in the recursive function call stack.
🧩 Duplicate input items & Pick each item once & No requirement for k
kNotes
Dedup: Need to sort the input first & skip items if
i != startIndex && input[i] == input[i-1]in for loopstartIndex: Inforloop of each level, pass, the currenti+1asstartIndexfor the next level, so that each item can be picked only once.Prune: Since the input is sorted first, this logic can be put into
forloop to skip all other items within the same level if it already does not satisfy the requirement.Time complexity: C(n,n)+C(n,n−1)+...+C(n,1)=2n. This is just the number of all combinations.
🌟 🟠 LC 40. Combination Sum II
TC: O(n∗2n). For each combination, we also need to copy
pathintoresultwhich is O(n)SC: O(n)
🌟 LC 90. Subsets II
Optimal Answer. TC: O(n∗2n). SC: O(n)
⚪ LC 491. Non-decreasing Subsequences
A variant of the above "LC 90. Subsets II". The difference is
Only collect results in some nodes with size >= 2
Cannot sort array to do dedup because it requires subsequence. Create local
usedvariable usingSet/int[]in each level.
Optimal Answer. TC: O(n∗2n). SC: O(n)
🧩 Unique input items & Pick each item once & No requirement for k
k🟠 LC 131. Palindrome Partitioning
It's a variation of combination problems. Choose the place to do the partition, fix the left part, and then go to the next level to process the right side recursively
Optimation
Calculate Palindrome
dp[][]in advance, so we do not need to check if each substring is a Palindrome during backtrackingUse same
StringBuilderin the same level to store the left part
TC: O(n∗2n)
2n combinations: C(n,1)+C(n,2)+...+C(n,n)=2n. (Cut once possibilities + Cut twice possibilities +...)
Need to copy
pathintoresultfor each Combination
SC: O(n2)
🔴 LC 93. Restore IP Addresses
Similar to the above LC 131, it is a string partition problem. Very easy to make mistakes in implementation.
Optimization
Convert input
StringtoStringBuilderto modify it directly.
TC: O(34)=O(1). The IP address can contain a maximum of 4 numbers, and each number can be split in up to 3 possible ways. Therefore, the maximum depth of the search tree is 4, and each node can have up to 3 child nodes.
SC: O(1)
🌟 LC 78. Subsets
Most classic subsets problem. See notes under Combination.
TC: O(n∗2n), SC: O(n)
Permutations
Notes
Pick
k(ifk<n, it's a partial permutation) or any number of items fromnitems w/o special requirement Order of result MATTERSDedup
Use
used[](used.length == nums.length) to dedup in tree branch direction for all permutation problems regardless of unique input or duplicate input.used[]is similar to thestartIndexused in combination problems.
Collect results in leaf nodes.
🧩 Unique input items & Pick each item once & k is a constant
k is a constant🌟 LC 46. Permutations
Optimal Answer, TC: O(n∗n!), SC: O(n)
🧩 Duplicate input items & Pick each item once & k is a constant
k is a constantNotes
Dedup: In addition to
used[]to dedup in branch direction, we also need to dedup in tree level direction. There are 2 ways.Method1: Sort the array first, then skip the item if
i!=0 && nums[i] == nums[i-1] && used[i-1] == falseinforloopMethod2: Create
used2[](used2.length == value range of nums) orSetin each level, then skip the item ifused2[nums[i]]orset.contains(nums[i])in for loop.
2D Backtracking
🌟 🟠 LC 51. N-Queens
Time Complexity: O(n2∗n∗n!), n2 for storing the result. n for checking validation. n! for all valid placements.
Better solution to speed up validation,
Approach 1: use 3 extra
boolean[], 1st for column direction. Other 2 for diagonal direction. For how to know the diagonal index in those 2boolean[]for a given point
In an nxn grid there are 2n - 1 diagonals going from top left corner to bottom right corner and there are 2n-1 diagonals going from top right corner to bottom left corner. The queen can run along both of these diagonal directions so we store both of them in boolean[]s. We need to map every index to both of these diagonal boolean arrays to that indexes on the same diagonal map to the same index in the boolean array as seen below: Diagonals this way / [ 0, 1, 2] [ 1, 2, 3] [ 2, 3, 4] need to map to boolean[0,1,3,4] of size 2n - 1 with the relation row + col. Diagonals this way \ [ 2, 1, 0] [ 3, 2, 1] [ 4, 3, 2] need to map to boolean[0,1,2,3,4] of size 2n - 1 with the relation row + n - col - 1.
Approach 2: use 3 extra
Set, 1st for column direction. Other 2 for diagonal direction. For each position(r,c)column set: each position is encoded to
cdiagonals set in this way \ : each position is encoded to
r-cdiagonals set in this way / : each position is encoded to
r+c
Answer. TC: O(n!), SC: O(n2)
This is not the optimal solution with validation speedup but it's more understandable.
⚪ LC 52. N-Queens II
Trick: Use 3 sets to speed up position checking logic.
Optimal Answer. TC: O(n!), SC: O(n)
🟠 LC 37. Sudoku Solver
I come up with a solution which is different from 代码随想录. The main difference is that each level directly checks the position that needs to be visited as the next one instead of doing a traversal from
board[0][0].Time complexity: 981 in the worst case
Structured Generation with Constraints
LC 22. Generate Parentheses
TC: O(n∗22n). The accurate one is actually O(4n/n), this number is related to the Catalan number. It should be out of scope for interview.
SC: O(n)
🟠 LC 3481. Apply Substitutions
Let n be the size of
replacements, m be the average length ofreplacementsvalue'sTC: O(n+n2∗m+n∗m) ⇒ O(n2∗m)
SC: O(n)
Other Backtracking Problems
🔴 LC 332. Reconstruct Itinerary
Method 1: Sort
<List<List<String>> ticketsby arrivals, then we can use the classic way to solve it withused[]. But this way will cause the Time Limit ExceededMethod 2: Convert
<List<List<String>> ticketstoMap<String, Map<String, Integer>>. The nestedMapshould beTreeMapand it contains arrival and its count.This is also TLE now. F**k 😄
For those who get TLE on 80/81, don't by disheartened because some sick guy who has no life wants to show that he can screw up backtracking by creating a very dense graph, it is not your problem to not coming up with a pruning trick for certain use cases.
It also a joke to expect someone come's up with an Eularian path finding algo without knowing it (which is the efficient algo for this type of problem). BTW, also don't read some of the "smart" solutions here that just copy the above algorithm and claims it is "just dfs" (although the code might look deceptively simple), these people have no idea why it works just pretend they have.
🔴 LC 679. 24 Game
Optimal Answer. TC: O(n3∗n!∗(n−1)!∗3n−1), SC: O(n2)
🔴 LC 465. Optimal Account Balancing
Key Insight
Let n be the number of people with non-zero balances.
The optimal number of transactions is at most n−1, but this is not a tight bound for all cases. To understand it, think about selecting one person as a pivot and settling all others against it. Since the total balance is zero, the pivot will be automatically settled at the end. Therefore, the optimal number of transactions is always ≤ n−1.
The minimum number of transactions is not always n−1; that is only a lower bound. The problem can be reframed as partitioning these balances into disjoint groups such that each group sums to zero. For a group of size k, at least k−1 transactions are required. Therefore, the total number of transactions equals:
∑(k−1) ⇒ n−g
where g is the number of zero-sum groups.
Thus, the problem reduces to maximizing the number of zero-sum groups.
The DFS solution effectively enumerates all possible ways to merge balances (i.e., all possible groupings), and selects the one that produces the maximum number of zero-sum groups, which guarantees the minimum number of transactions.
Answer. TC: O((n−1)!), SC: O(n)
Last updated