题目链接:77. 组合 - 力扣(LeetCode)
作者思考:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
首先组合问题要先搞清楚什么是组合,组合是元素无序的存放到集合中,和排列不一样,排列是有序的。在组合中[1,2]和[2,1]是同一种情况,但是在排列中就是属于两种情况。
搞清楚什么是组合后,我们再来分析问题。
如果k比较少的情况,我们可以直接for循环进行嵌套,那么如果k比较大,那么我们就要嵌套k层for循环,这想想也是不现实的。那么用什么办法好呢?
这时就可以用我们之前学到的回溯思想。
如图
有递归就有回溯,可以说回溯是递归的附加物。
那么说到了递归,肯定要说自我问答一下,递归三部曲。
①确定返回值和参数
本题的结果集是List<List<Integer>> 那么我们用result来接收这个结果;树枝就是路径,用List<Integer> path;定义一个变量来表示 下一次的元素将要进行组合的位置。
②确定终止条件
在上图中,每次集合中存够了k个元素就 退出当前的递归。那么终止条件是
if (path.size() == k) {//当单个结果长度等于目标k的长度
result.add(new ArrayList<>(path));//将path添加到结果集中
return ;
}
③单层递归逻辑
我们的递归逻辑是表示二叉树的深度,单层的递归逻辑就是二叉树的宽度(横向)。
for (int i = startindex; i <= n ; i++) {
path.add(i);
backtracking(n, k, i +1);//递归剩下元素
path.remove(path.size() -1);//回溯的过程 将本次添加元素删除 给下一个元素留位置
}
完整代码
class Solution {
List<List<Integer>> result = new ArrayList<>();//存放结果集
List<Integer> path = new ArrayList<>();//存放单个结果
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
void backtracking(int n, int k, int startindex) {
//确定递归的终止条件
if (path.size() == k) {//当单个结果长度等于目标k的长度
result.add(new ArrayList<>(path));//将path添加到结果集中
return ;
}
for (int i = startindex; i <= n; i++) {
path.add(i);
backtracking(n, k, i +1);//递归剩下元素
path.remove(path.size() -1);//回溯的过程 将本次添加元素删除 给下一个元素留位置
}
return ;
}
}
本题由于是组合问题,我们想想可不可以进行剪枝优化。
如图,为什么要进行剪枝操作:当我们n = 4, k = 4的时候,进行第一层for循环后,取2操作已经没有意义了,因为组合中的无序性,在取2的时候元素中不可能出现元素1,那集合的长度也不可能符合题目要求k = 4。如果我们不进行优化,我们需要进行整树的遍历,实际上该例子我们只遍历了树的一条树枝,之后的遍历都没有意义。
故我们只需要优化单层递归的范围。
1.选取过的元素的集合长度:path.size()
2.未选取过的元素的集合长度:k - path.size()
3.在集合中至多要从该起始位置:n - (k - path.size()) + 1
优化后完整代码
class Solution {
List<List<Integer>> result = new ArrayList<>();//存放结果集
List<Integer> path = new ArrayList<>();//存放单个结果
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
void backtracking(int n, int k, int startindex) {
//确定递归的终止条件
if (path.size() == k) {//当单个结果长度等于目标k的长度
result.add(new ArrayList<>(path));//将path添加到结果集中
return ;
}
for (int i = startindex; i <= n - (k - path.size()) + 1; i++) {
path.add(i);
backtracking(n, k, i +1);//递归剩下元素
path.remove(path.size() -1);//回溯的过程 将本次添加元素删除 给下一个元素留位置
}
return ;
}
}