技巧:
经常需要一个start来标记开始选择的起始位置来达到去重的目的
C++ 总结了回溯问题类型 带你搞懂回溯算法(大量例题)
https://leetcode-cn.com/problems/subsets/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-gao-/
- ①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
- ②根据题意,确立结束条件
- ③找准选择列表(与函数参数相关),与第一步紧密关联※
- ④判断是否需要剪枝
- ⑤作出选择,递归调用,进入下一层
- ⑥撤销选择
C++ 总结了回溯问题类型 带你搞懂回溯算法(排列篇)
https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-ga-4/
中等
78.子集
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
List<Integer> track=new ArrayList<>(); //走过的路径
backtarck(nums,0,track);
return res;
}
public void backtarck(int[] nums,int start,List<Integer> track){
res.add(new ArrayList<>(track));
for(int i=start;i<nums.length;i++){
// 做选择(添加这个数)
track.add(nums[i]);
// 递归回溯(从i+1开始)
backtarck(nums,i+1,track);
// 撤销选择
track.remove(track.size() - 1);
}
}
}
77. 组合
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
if(k<=0 || n<=0)
return res;
List<Integer> track=new ArrayList<>(); //走过的路径
backtarck(n,k,1,track);
return res;
}
public void backtarck(int n,int k,int start,List<Integer> track){
if(k==track.size()){
res.add(new ArrayList<>(track));
return;
}
for(int i=start;i<=n;i++){
// 做选择(添加这个数)
track.add(i);
System.out.println("递归之前 => " + track);
// 递归回溯(从i+1开始)
backtarck(n,k,i+1,track);
// 撤销选择
track.remove(track.size() - 1);
System.out.println("递归之后 => " + track);
}
}
public static void main(String[] args) {
Solution solution = new Solution();
int n = 5;
int k = 3;
List<List<Integer>> res = solution.combine(n, k);
System.out.println(res);
}
}
递归之前 => [1]
递归之前 => [1, 2]
递归之前 => [1, 2, 3]
递归之后 => [1, 2]
递归之前 => [1, 2, 4]
递归之后 => [1, 2]
递归之前 => [1, 2, 5]
递归之后 => [1, 2]
递归之后 => [1]
递归之前 => [1, 3]
递归之前 => [1, 3, 4]
递归之后 => [1, 3]
递归之前 => [1, 3, 5]
递归之后 => [1, 3]
递归之后 => [1]
递归之前 => [1, 4]
递归之前 => [1, 4, 5]
递归之后 => [1, 4]
递归之后 => [1]
递归之前 => [1, 5]
递归之后 => [1]
递归之后 => []
递归之前 => [2]
递归之前 => [2, 3]
递归之前 => [2, 3, 4]
递归之后 => [2, 3]
递归之前 => [2, 3, 5]
递归之后 => [2, 3]
递归之后 => [2]
递归之前 => [2, 4]
递归之前 => [2, 4, 5]
递归之后 => [2, 4]
递归之后 => [2]
递归之前 => [2, 5]
递归之后 => [2]
递归之后 => []
递归之前 => [3]
递归之前 => [3, 4]
递归之前 => [3, 4, 5]
递归之后 => [3, 4]
递归之后 => [3]
递归之前 => [3, 5]
递归之后 => [3]
递归之后 => []
递归之前 => [4]
递归之前 => [4, 5]
递归之后 => [4]
递归之后 => []
递归之前 => [5]
递归之后 => []
[[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/combinations/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-ma-/
216. 组合总和 III
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
List<Integer> track=new ArrayList<>(); //走过的路径
backtarck(k,n,1,0,track);
return res;
}
// sum 为已经收集的元素之和
public void backtarck(int k, int n,int start,int sum,List<Integer> track){
if(sum==n&&track.size()==k){
res.add(new ArrayList<>(track));
return;
}
for(int i=start;i<10;i++){
// 做选择(添加这个数)
track.add(i);
sum += i;
// 递归回溯
backtarck(k,n,i+1,sum,track); // 注意下一步start=i+1
// 撤销选择
sum -= i;
track.remove(track.size() - 1);
}
}
}
39. 组合总和
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<Integer> track=new ArrayList<>(); //走过的路径
backtarck(candidates,target,0,0,track);
return res;
}
public void backtarck(int[] candidates,int target,int sum,int start,List<Integer> track){
if (sum > target) {
return;
}
if(sum==target){
res.add(new ArrayList<>(track));
return;
}
for(int i=start;i<candidates.length;i++){
// 做选择
track.add(candidates[i]);
sum+=candidates[i];
// 递归回溯
backtarck(candidates,target,sum,i,track);// 可以重复读取当前的数,下一次start=i
// 撤销选择
sum-=candidates[i];
track.remove(track.size() - 1);
}
}
}
40.组合总和 II ?
去重没看懂
for 枚举出选项时,加入下面判断,从而忽略掉同一层重复的选项,避免产生重复的组合。比如[1,2,2,2,5],选了第一个 2,变成 [1,2],它的下一选项也是 2,跳过它,因为如果选它,就还是 [1,2]。
作者:xiao_ben_zhu
链接:https://leetcode-cn.com/problems/combination-sum-ii/solution/man-tan-wo-li-jie-de-hui-su-chang-wen-shou-hua-tu-/
首先,
第一个操作:
- 之前选择过的不用再去选择
这说明第一次我们的选择列表是[1,2,2,2,5],那第二次我们的选择列表就应该是[2,2,2,5]。
第二个操作为:
- 如果以当前结点为头结点的所有组合都找完了,那么下一个与他他相同的头结点就不用去找了。
其中i>start一定要理解,i是当前考察的元素下标,start是本层最开始的那个元素的下标,我们的去重是要同层去重
https://leetcode-cn.com/problems/combination-sum-ii/solution/he-xin-kao-dian-tong-ceng-qu-zhong-by-jin-ai-yi/
// i>start 防止某个元素还没来得及使用就因为跟前一个元素相同而直接被过滤 ??
// 为了防止数组越界(当start=0)
if(i > start && candidates[i] == candidates[i - 1]) {
continue;
}
作者:Geralt_U
链接:https://leetcode-cn.com/problems/combination-sum-ii/solution/40-zu-he-zong-he-ii-by-ming-zhi-shan-you-m9rfkvkda/
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<Integer> track=new ArrayList<>(); //走过的路径
Arrays.sort(candidates);
backtarck(candidates,target,0,0,track);
return res;
}
public void backtarck(int[] candidates,int target,int sum,int start,List<Integer> track){
if (sum > target) {
return;
}
if(sum==target){
res.add(new ArrayList<>(track));
return;
}
for(int i=start;i<candidates.length;i++){
// 这里去重是关键
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
// 做选择
track.add(candidates[i]);
sum+=candidates[i];
// 递归回溯
backtarck(candidates,target,sum,i+1,track);// 下一次start=i+1
// 撤销选择
sum-=candidates[i];
track.remove(track.size() - 1);
}
}
}