力扣 77 组合
全部刷题与学习记录
原题目
题目地址:https://leetcode-cn.com/problems/combinations/
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
考查知识点
回溯:可以参考回溯三步法【算法套路】-【回溯篇】【回溯三步法】
好的解法
这道题是参考【代码随想录】大佬的回溯算法:求组合问题!来写的心得体会。
首先我们按照 k
的取值不同来分析一下这道题,k=2
时,先从[1,2,3,4]中随机选取一个数,这里假设选了 1 ,那么第二次选取数字的范围就缩小成了[2,3,4], 同理,如果第一次随机选取了 2 ,那么第二次选取数字的范围就是[3,4]。注意这里之所以是[3,4]而不是[1,3,4]是因为这是个组合问题,并不是排列。
第一次选取范围 第一次选取 第二次选取范围 第二次选取 选取结果
[1,2,3,4] 1 [2,3,4] 2 1,2
[1,2,3,4] 1 [2,3,4] 3 1,3
[1,2,3,4] 1 [2,3,4] 4 1,4
[1,2,3,4] 2 [3,4] 3 2,3
[1,2,3,4] 2 [3,4] 4 2,4
[1,2,3,4] 3 [4] 4 3,4
很显然,k
的取值就是选取数字的次数,如果使用for循环来解决这个问题的话,只有当k
为常数的时候,才可以明确for循环会嵌套使用多少层。那么对于k
是变量的情况,是不能用嵌套k层for循环来解决的,因为写不出来。
那么针对k
层嵌套循环才能解决的问题,用什么来替代for循环呢?答案就是递归。在一个递归函数中写一个for循环,那么需要嵌套k
层循环就变成了一个递归函数递归k
次。

按照回溯三步法来考虑这个回溯问题:
1、函数参数和返回值:返回值一般为void;因为要把符合条件的结果存入数组,所以需要一个存放结果的集合vector<vector<int>> result
,需要一个变量来记录每一个结果vector<int> path
。又因为这是一个组合问题,每一次选取数字后,选取范围都要向后移动,所以需要一个开始下标startIndex
表示选取范围的开始
2、终止条件:当path
中数字的数量==k
时,说明找到一个结果,此时将path
存入result
3、单层处理逻辑:到了这里就说明上一步终止条件并没有被满足,那么就要从startIndex
开始的范围内选取一个数字存入path
,并且向下递归,注意此时startIndex
应该++;与一般的递归不一样,回溯问题千万不能忘记撤销操作
下面是代码:
class Solution {
private:
vector<vector<int>> result; //存放结果的集合
vector<int> path; //结果
void backTracking(int n, int k, int startIndex) {
//1、回溯函数参数以及返回值
if (path.size() == k) {
//2、终止条件
result.push_back(path);
return;
}
//3、单层处理逻辑
for (int i = startIndex; i <= n; ++i) {
path.push_back(i);
backTracking(n, k, i + 1);
path.pop_back(); //4、回溯,撤销操作
}
}
public:
vector<vector<int>> combine(int n, int k) {
backTracking(n, k, 1);
return result;
}
};
剪枝
其实这道题按照上面的代码来说还是可以进行剪枝的,比如下面这种情况:
n = 4 k = 3
第一次选取范围 第一次选取 第二次选取范围 第二次选取 选取结果
[1,2,3,4] 1 [2,3,4] 2 1,2
......
需要剪枝的情况
[1,2,3,4] 3
[1,2,3,4] 4
这是由于第一次取到的数字加上之后所有数字也不能满足path.size() == k
,所以对于for循环的循环变量i
结束时取值要进行约束,比如上面情况中就不必取到i = 3
和i = 4
两种情况
因此,for循环修改为:
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
// 剪枝
path.push_back(i); // 处理节点
backtracking(n, k, i + 1);
path.pop_back(); // 回溯,撤销处理的节点
}
注意这个循环变量i
的终止条件可以理解为:i-1+(k-path.size()) <= n
。从i
开始还需要k-path.size()
个元素,但此时i
还没加进path中,所以实际起始元素是i-1
,即i-1+(k-path.size()) <= n