思路转换:从n个数中选k个数的组合可以看成是长度固定的子集,可以转化为子集型回溯+路径长度判断。
优化:可以进行剪枝,设path长为m,那么还需要选d=k - m个数,设当前需要从[1,i]这i个数中选数如果i<d,最后必然无法选出k个数不需要继续递归,这是一种剪枝技巧。
例题:77. 组合
class Solution {
private int k;
private final List<Integer> path = new ArrayList<>();
private final List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
this.k = k;
dfs(n);
return ans;
}
private void dfs(int i) {
int d = k - path.size(); // 还要选 d 个数
if (d == 0) {
ans.add(new ArrayList<>(path));
return;
}
for (int j = i; j >= d; --j) {
path.add(j);
dfs(j - 1);
path.remove(path.size() - 1);
}
}
}
时间复杂度:分析回溯问题的时间复杂度,有一个通用公式:路径长度×搜索树的叶子数。对于本题,它等于o(k·C(n,k))
例题:例题:77. 组合
此题相比于上一题基本上就是增加了两个剪枝条件:
当所选数值大于目标值时,直接返回;
当所能选择的最大值小于目标值时,直接返回。(该值可以用求和公式)∑=(首数值+末数值)×(数列个数/2)
代码如下:
class Solution {
private int k;
private final List<Integer> path = new ArrayList<>();
private final List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
this.k = k;
dfs(9, n);
return ans;
}
private void dfs(int i, int t) {
int d = k - path.size(); // 还要选 d 个数
if (t < 0 || t > (i * 2 - d + 1) * d / 2) // 剪枝
return;
if (d == 0) {
ans.add(new ArrayList<>(path));
return;
}
for (int j = i; j >= d; --j) {
path.add(j);
dfs(j - 1, t - j);
path.remove(path.size() - 1);
}
}
}