Backtracking has many different scenarios. Backtracking is actually DFS + pruning. It's almost enumeration, and then either finds that the branch that meets the conditions, or the current branch can no longer meet the conditions, pruning and giving up.
Let's write about the classical problem of sorting and combining.
subject | brief introduction | candidates Whether there are duplicate elements |
Element allowed or not Reuse |
With start |
---|---|---|---|---|
39. Combination Sum | A combination whose sum is k | Element distinct | Allow multiple uses | Yes |
40. Combination Sum II | A combination whose sum is k | Elements can have duplicates | There are several limits | Yes |
78. Subsets | Find all subsets | Element distinct | What's the use | Yes |
90. Subsets II | Find all subsets | Elements can have duplicates | There are several limits | Yes |
46. Permutations | Find all permutations | Element distinct | -- (full use) | nothing |
47. Permutations II | Find all permutations | Elements can have duplicates | -- (full use) | nothing |
77. Combinations (class 78) | All sizeK subsets | Element distinct | Use k | Yes |
- Combination: start is required to limit the current layer to be selected from only one range (not the whole set), because some of them have been used. The combination problem is considered from the perspective of "whether to use the current resource". resource1 can be used or not. After the decision, resource2 can be used or not When waiting for the current layer, such as resource M, the previous M has been considered (and a decision has been made), so the current layer only considers the resources from M to the last part (that is, starting from start).
- Sort: start is not required. Because the sorting problem is considered from the perspective of "what are the legal candidate s for the current position". Candidates (i.e. resources) are sorted according to resources. The combination problem must be in order from the perspective of "whether to use the current resources". Therefore, start can be used to mark. Here, only a whole array of used [] can be used to record which resources have been used.
39. Combination Sum
Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
[7],
[2,2,3]
] candidates Chinese elements can be used countless times
The elements in this question candidates = [2,3,6,7] are allowed to be used indefinitely. If all combinations are found, there are infinite many. Fortunately, there is also a limit with target=7.
Because the candidates element is allowed to be used indefinitely, when you transfer start to the next level, you transfer i instead of i+1, and the next level still processes the current bit. Isn't that the one who never jumps next? That's right. 2, 2, 2 It's one of the branch es, until the total exceeds target, which does not meet the requirements, and is pruned.
If the candidates element is distinct, there is no need to sort. Sorting exists in the case of "candidates may have duplicate elements". Ensure that two elements with the same value are next to each other. We don't care about the value size relationship of the element itself.
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> lst = new ArrayList<>(); genCombination(lst, new ArrayList<Integer>(), candidates, target, 0); return lst; } private void genCombination(List<List<Integer>> lst, List<Integer> path, int[] candidates, int remain, int start) { if (remain == 0) {//good terminal lst.add(new ArrayList<>(path));//Note: new a new ArrayList return; } else if (remain < 0) {//bad terminal return; } for (int i = start; i < candidates.length; i++) { path.add(candidates[i]); genCombination(lst, path, candidates, remain-candidates[i], i); path.remove(path.size()-1); } } }
40. Combination Sum II
Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
] difference: candidates Each element in can only be used once
- 39: candidates element is allowed to be used indefinitely. i will be passed when i pass start next level.
- 40: candidates element is only allowed to be used once, and i+1 is passed when start is passed down.
There may be duplicate elements in candidates, so you need to sort [1,1,2,5,6,7,10] before backtracking to ensure that the elements with the same value are next to each other. Next to that, we're going to think "do we need to skip repeating elements?". Note that not all elements with the same value are skipped, [1,1,2 ]When processing the second 1, you cannot skip it, otherwise [1, 1, 6] will be missed.
- The "skip repetition" here refers to that if there is a repetition in the following number, such as [1,1,2,5,6,6,7,10], the second 6 will be skipped and will not be processed (same level horizontal - skip).
- And "1 of the previous layer and 1 of the current layer" is not skipped.
Use I > Start & & candidates [i] = = candidates [I-1] to define the skip condition, and exclude i = = start, that is, the first one in this layer (no skip).
class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); List<List<Integer>> lst = new ArrayList<>(); List<Integer> path = new ArrayList<>(); getCombination(lst, path, candidates, target, 0); return lst; } private void getCombination(List<List<Integer>> lst, List<Integer> path, int[] candidates, int remain, int start) { if (remain == 0) {//good terminal lst.add(new ArrayList<>(path)); return; } else if (remain < 0) {//bad terminal return; } for (int i = start; i < candidates.length; i++) { if (i > start && candidates[i] == candidates[i-1]) {continue;} path.add(candidates[i]); getCombination(lst, path, candidates, remain-candidates[i], i+1); path.remove(path.size()-1); } } }
78. Subsets
Input: nums = [1,2,3], Input element distinct
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
39,40 combination sum takes "and reach target" as the success condition of a branch. 78,90 subset s have no identification standard, and all branches are required. In this case, it must be required that "the element cannot be used indefinitely", otherwise it will not be finished.
78 elements distinct, 90 allow to have duplicate elements, but they can be used at most when there are several values. Therefore, from the perspective of "whether to use the current resource", this one can't be used anymore after being used. Pass i+1 to the next level.
78 element distinct, there is no need for sorting, and there is no problem of skipping (skipping only needs to be considered if there are duplicate elements and sorted).
class Solution { public List<List<Integer>> subsets(int[] nums) { List<List<Integer>> list = new ArrayList<>(); getSubset(list, new ArrayList<Integer>(), nums, 0); return list; } private void getSubset(List<List<Integer>> list, List<Integer> path, int[] nums, int start) { list.add(new ArrayList<>(path));//Add path to the list anyway for (int i = start; i < nums.length; i++) { path.add(nums[i]); getSubset(list, path, nums, i+1); path.remove(path.size()-1); } } }
90. Subsets II
Input: [1,2,2] Different: Input There may be duplicate elements
Output:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
Almost the same as 78, except for 78 element distinct, 90 allows duplicate elements. You need to sort, and you need to think about skipping. The skip method is the same as 40. Combination Sum II.
class Solution { public List<List<Integer>> subsetsWithDup(int[] nums) { List<List<Integer>> lst = new ArrayList<>(); Arrays.sort(nums); getSubset(lst, new ArrayList<Integer>(), nums, 0); return lst; } private void getSubset(List<List<Integer>> lst, List<Integer> path, int[] nums, int start) { lst.add(new ArrayList<>(path)); for (int i = start; i < nums.length; i++) { if (i > start && nums[i] == nums[i-1]) {continue;} path.add(nums[i]); getSubset(lst, path, nums, i+1); path.remove(path.size()-1); } } }
46. Permutations
Input: [1,2,3],Array element distinct
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
The above four are combinations. This is an arrangement. Because all elements need to be used, the order of using elements and input is not necessarily the same, so start cannot be used to limit the optional range.
How do you know which elements have been used? path.contains(nums[i]) as the check used condition. Because the values of each element are different, it's OK to check whether it has appeared directly in the ArrayList.
Note here that the check used condition is different from the skip condition. See explanation 47 below.
class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> ret = new ArrayList<>(); backtracking(ret, new ArrayList<>(), nums); return ret; } private void backtracking(List<List<Integer>> list, List<Integer> path, int[] nums) { if (path.size() == nums.length) { list.add(new ArrayList<>(path)); return; } for (int i = 0; i < nums.length; i++) { if (path.contains(nums[i])) {continue;} path.add(nums[i]); backtracking(list, path, nums); path.remove(path.size()-1); } } }
47. Permutations II
Input: [1,1,2] difference: input Array may have duplicate
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
- 46: "check if used" condition: path.contains(nums[i]). Because the values of each element are different, it's OK to check whether it has appeared directly in the ArrayList.
- 47: "check whether used" condition: used[i] == true. Because there may be elements with the same value, a separate array used [] is needed to record whether each element has been used.
The check used condition is different from the skip condition.
47 there is also a "skip" condition: similar to 40. Combination Sum II, 90. Subsets II.
i > 0 && nums[i] == nums[i-1] && !used[i-1]
The differences between 40 and 90 are as follows:
- 40,90: i = = start do not skip, that is, if the first element of each layer is the same as the element of the previous layer, do not skip. [1,1,2,5,6,7,10], that is, when processing the call of [1,1], the second 1 is not skipped.
- 47: used[i-1]= = true do not skip, that is, if the former element of the same two elements has been used, the latter will not skip.
In fact, the two situations are essentially the same. Both are: if the former is not used, the latter will skip (because everything is processed in the former). If the former is used, the latter cannot skip, and both [1] and [1,1] need to be considered. (40,90 because of sequential processing, i= =start is the case of "the former has been used, then the latter cannot be skipped").
class Solution { public List<List<Integer>> permuteUnique(int[] nums) { List<List<Integer>> lst = new ArrayList<>(); Arrays.sort(nums); boolean[] used = new boolean[nums.length]; getPermutation(lst, new ArrayList<Integer>(), nums, used); return lst; } private void getPermutation(List<List<Integer>> lst, List<Integer> path, int[] nums, boolean[] used) { if (path.size() == nums.length) { lst.add(new ArrayList<>(path)); return; } for (int i = 0; i < nums.length; i++) { if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) {continue;}//Skip condition if (used[i] == true) {continue;}//Check if it has been used path.add(nums[i]); used[i] = true; getPermutation(lst, path, nums, used); used[i] = false; path.remove(path.size()-1); } } }
77. Combinations
Input: n = 4, k = 2
Output:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
Almost the same as 78. Subsets, except that only path with length of 2 is considered successful (only added to the result list). No code here.