有部分图片节选自代码随想录
问题描述:
思考:如果k为2,可以用两层for。k为3可以用三层for,但是k为50,n为100呢?
Q&A:要解决 n为100,k为50的情况,暴力写法需要嵌套50层for循环,那么回溯法就用递归来解决嵌套层数的问题。递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了。
用递归来代替需要循环的次数
我的实现:
class Solution {
LinkedList<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
LinkedList<Integer> list = new LinkedList<>();
backtracing(list,1, 1, n, k);
return result;
}
public void backtracing(List<Integer> list,int index, int round, int n, int k) {
if (round > k) {
return;
}
for (int i = index; i <= n; i++) {
list.add(i);
if (round == k) {
LinkedList<Integer> copy = new LinkedList<>();
for (Integer item : list) {
copy.add(item);
}
result.add(copy);
}
backtracing(list, i + 1, round + 1, n, k);
list.removeLast();
}
}
}
可优化的地方:
1、关于循环次数,不需要传入一个round,可以直接用list的size来判断
2、list可以定位全局变量
3、可以剪枝优化
随想录优化版本:
class Solution {
List<List<Integer>> result= new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
public void backtracking(int n,int k,int startIndex){
if (path.size() == k){
result.add(new ArrayList<>(path));
return;
}
for (int i =startIndex;i<=n;i++){
path.add(i);
backtracking(n,k,i+1);
path.removeLast();
}
}
}
剪枝:省去不必要的运算
假如n=4, k=4,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。
剪枝后的版本:
lass Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
combineHelper(n, k, 1);
return result;
}
/**
* 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
* @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
*/
private void combineHelper(int n, int k, int startIndex){
//终止条件
if (path.size() == k){
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){
path.add(i);
combineHelper(n, k, i + 1);
path.removeLast();
}
}
}
题目描述:
仿照上面的思路,得到以下代码:
class Solution {
private int sum = 0;
private ArrayList<List<Integer>> result = new ArrayList<>();
private ArrayList<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracing(k, n, 1);
return result;
}
public void backtracing(int k, int n, int startIndex) {
if (sum == n && path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <= 9; i++ ) {
path.add(i);
sum += i;
backtracing(k, n, i + 1);
sum -= i;
path.removeLast();
}
}
}
没有组合数量限制的组合:
限制:数组没有重复元素、同一个数字可以无限使用
难点:同一个数字可以无限使用====⇒解决方案:将sum>target作为限制个数的隐形条件
与39的区别:数组candidates元素是存在重复的。在每个组合里每个元素只能用一次
难点:因为这题数组candidates元素是存在重复的,所以实例1中排序完:[1,1,2,5,6,7,10]
如果不加筛选,最后结果会出现[[1,1,6],[1,2,5],[1,7],[1,2,5],[1,7],[2,6]]这样重复的元素
我的实现:
class Solution {
ArrayList<List<Integer>> result = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtracing(0, candidates, target);
//HashSet<List<Integer>> set=new HashSet<>(result);
//return new ArrayList<List<Integer>>(set);
return result;
}
public void backtracing(int startIndex, int[] candidates, int target) {
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
if (sum > target) {
return;
}
for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++) {
if (i != startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
sum += candidates[i];
path.add(candidates[i]);
backtracing(i + 1, candidates, target);
sum -= candidates[i];
path.removeLast();
}
}
}
剪枝:在for循环中添加sum + candidates[i] <= target判定条件