题目1、子集
题解
又是回溯法,这次能根据模板写个大概,但是如何去重、控制循环出口仍然是个问题
在题解中看到一篇“授人以渔”的文章,C++ 总结了回溯问题类型 带你搞懂回溯算法(大量例题),总结了回溯问题的三种类型和对应解决思路
总结:子集、组合类问题,关键是用一个 idx 参数来控制选择列表!!最后回溯六步走:
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
子集的递归树:
class Solution {
int n;
List<List<Integer>>res=new ArrayList<>();
List<Integer>combine=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
n=nums.length;
backtrack(nums,0);
return res;
}
public void backtrack(int[] nums,int idx){
//所有路径都应该加入结果集
res.add(new ArrayList<>(combine));
for(int i=idx;i<n;i++){
combine.add(nums[i]);
//递归 不能重复使用当前数 因此下一轮从i+1开始
backtrack(nums,i+1);
combine.remove(combine.size()-1);
}
}
}
时间复杂度: O ( 2 n n ) O(2^nn) O(2nn),对于每个元素,有选或者不选两种,共 2 n 2^n 2n中状态,每种状态需要O(n)时间来构造子集
空间复杂度: O ( n ) O(n) O(n),递归栈和临时数组combine的代价
题目2、子集 II
题解
跟上一题的不同是,这里的整数数组可能包含重复元素,结果集中需要一些去重操作,所以在代码中多了排序和剪枝的步骤
class Solution {
List<Integer>set=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);//排序
backtrack(nums,0);
return res;
}
public void backtrack(int[] nums,int idx){
res.add(new ArrayList<>(set));
for(int i=idx;i<nums.length;i++){
//剪枝,去除子集中重复元素
if(i>idx&&nums[i]==nums[i-1])
continue;
set.add(nums[i]);
backtrack(nums,i+1);
set.remove(set.size()-1);
}
}
}
时间复杂度: O ( 2 n n ) O(2^nn) O(2nn),对于每个元素,有选或者不选两种,共 2 n 2^n 2n中状态,每种状态需要O(n)时间来构造子集
空间复杂度: O ( n ) O(n) O(n),递归栈和临时数组set的代价
题目3、组合
既然今天刚上了回溯,就再练几道,正好把中秋假期的提前写了
77. 组合【中等】
题解
这道也是回溯经典题,终于自己能写出来一道了哈哈,但是没有注意剪枝
class Solution {
List<Integer>set=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtrack(n,k,1);
return res;
}
public void backtrack(int n,int k,int start){
//剪枝:当前元素数目+[start,n]元素总数<k,直接丢掉
if(set.size()+(n-start+1)<k)
return;
if(set.size()==k){
res.add(new ArrayList<>(set));
return;
}
for(int i=start;i<=n;i++){
set.add(i);
backtrack(n,k,i+1);
set.remove(set.size()-1);
}
}
}
时间复杂度: O ( ( n k ) × k ) O(\left( \begin{array}{c} n\\ k\\ \end{array} \right )\times k) O((nk)×k),组合枚举数*每次记录答案复杂度
空间复杂度: O ( k ) O(k) O(k),递归栈和临时数组set的代价
这道题剪枝和不剪枝时间差距还挺大的: