①、组合
给定两个整数
n
和k
,返回范围[1, n]
中所有可能的k
个数的组合。你可以按 任何顺序 返回答案。
事例:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
若用纯for循环嵌套迭代的话,k = 2时:
private List<List<Integer>> res = new ArrayList<>();
private LinkedList<Integer> tmp = new LinkedList<>();
for(int i = 1;i <= n;i++){
tmp.add(i);
for(int j = i + 1;j <= n;j++){
tmp.add(j);
res.add(new ArrayList<>(tmp));
tmp.removeLast();
}
tmp.removeLast();
}
k等于多少时就需要套用多少层for循环,代码写死,没法动态设置管理。
故需要才用回溯思想,即在递归的条件下套用for循环,递归结束为当前组合满足题设k,for循环中处理单层递归,回溯。
以这道题为例,n = 4,k = 2时:
可以形象得当做一颗多叉树,在1的那颗子树还可以直接扩展到1234,即4层。
回溯代码:
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return res;
}
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> tmp = new LinkedList<>();
public void backtracking(int n,int k,int startIndex){
if(tmp.size() == k){
res.add(new ArrayList<>(tmp));
return;
}
for(int i = startIndex;i <= n - (k - tmp.size()) + 1;i++){
tmp.add(i);
//递归
backtracking(n,k,i + 1);
//回溯
tmp.removeLast();
}
}
backtracking函数中 for循环代表上图中第二层的每个子树,即1 2 3 4四个分支,由于递归都是往后递归i + 1,故不会出现重复的数,且不会得到逆序组合。由于需要回溯,故采用linkedList,方便删除最后的结点。
目标组合的size为k,故在tmp添加i的时候,当前组合tmp所缺的个数为k - tmp.size(),遍历到i时,只能递归添加后面的数,故当前的剩余数为n - i + 1,满足n - i + 1>k - tmp.size()时,才有必要递归添加。即修改for循环的条件为: i <= n - (k - tmp.size()) + 1,可以优化效率。