1.题目描述:
给定两个整数n和k,返回范围[1,n]
中所有可能的k个数的组合。你可以按任何顺序返回答案。
2.回溯算法概述:
①、回溯算法的本质是穷举,穷举所有可能,然后选出我们想要的答案,可以加入剪枝操作提高效率。②、回溯算法解决的问题都可以抽象为树形结构,回溯算法解决的都是在集合中递归查找子集,集合的大小(包括每级递归的子集合)为树的宽度,递归的层数为树的深度。递归存在终止条件,所以抽象树是一棵高度有限的N叉树。③、回溯算法抽象树示意图+代码框架(类似递归三部曲)。上述部分参考代码随想录。
3.代码:
class Solution {
private List<List<Integer>> resList = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTracking(n, k, 0);
return resList;
}
public void backTracking(int n, int k, int index) {//index用来缩小子集
if (list.size() == k) {
resList.add(new ArrayList<>(list));//直接add(list)最终全为null,回溯时list会被不断修改,最后add(4)后被remove为null
return;
}
for (int i = index; i < n; i++) {
list.add(i + 1);
backTracking(n, k, i + 1);//必须i + 1,不能index + 1
list.remove(list.size() - 1);//回溯删除上一步递归添加的数据,例如[1,2]还原为[1]在被递归掉用的循环中更新为[1,3]
}
}
}
4.剪枝优化:
如果for循环选择的起始位置之后的元素个数小于需要的元素个数(动态变化)则没有必要搜索,在for循环中优化即可。某次回溯中,list集合已有list.size()个元素,则还需要k - list.size()个元素,那么index索引位置后面至少还要有k - list.size()个元素,这里规定了index的上限,反映在代码里也就是n索引的位置,归纳一下有上限为n - (k - list.size()) + 1(索引从0开始)。
class Solution {
private List<List<Integer>> resList = new ArrayList<>();
private List<Integer> list = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTracking(n, k, 0);
return resList;
}
public void backTracking(int n, int k, int index) {
if (list.size() == k) {
resList.add(new ArrayList<>(list));
return;
}
for (int i = index; i < n - (k - list.size()) + 1; i++) {
list.add(i + 1);
backTracking(n, k, i + 1);
list.remove(list.size() - 1);
}
}
}