代码随想录二刷 |回溯 | 组合
- 题目描述
- 解题思路
- 代码实现
题目描述
77.组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
解题思路
递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了。
此时递归的层数大家应该知道了,例如:n为100,k为50的情况下,就是递归50层。
把组合问题抽象为如下树形结构:
可以看出这棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不再重复取。
第一次取1,集合变为2,3,4 ,因为k为2,我们只需要再取一个数就可以了,分别取2,3,4,得到集合[1,2] [1,3] [1,4],以此类推。
每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围。
图中可以发现n相当于树的宽度,k相当于树的深度。
图中每次搜索到了叶子节点,我们就找到了一个结果。
相当于只需要把达到叶子节点的结果收集起来,就可以求得 n 个数中 k 个数的组合集合。
回溯法三部曲:
-
递归函数的返回值和参数
定义两个全局变量,一个用来存放符合条件的单一结果,另一个存放符合条件结果的集合vector<vector<int>> result; // 存放符合条件结果的集合 vector<int> path; // 用来存放符合条件结果
函数里一定有两个参数,既然是集合n里面取k个数,那么n和k是两个int型的参数。
然后还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,…,n] )。
从下图中红线部分可以看出,在集合[1,2,3,4]取1之后,下一层递归,就要在[2,3,4]中取数了,那么下一层递归如何知道从[2,3,4]中取数呢,靠的就是startIndex。
所以需要startIndex来记录下一层递归,搜索的起始位置。
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int startIndex);
-
递归函数的终止条件
path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了。此时用result二维数组把path保存起来,并终止本层递归。
if (path.size() == k) { result.push_back(path); return; }
-
单层搜索的过程
for循环每次从startIndex开始遍历,然后用path保存取到的节点i。for (int i = startIndex; i < n; i++) { path.push_back(i); // 处理节点 backtracking(n, k, i + 1); // 递归,控制树的纵向遍历,注意下一层搜索要从i + 1 开始 path.pop_back(); // 回溯,撤销处理的节点 }
代码实现
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.push_back(path);
return;
}
for (int i = startIndex; i < n; i++) {
path.push_back(i);
backtracking(n, k, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> combine (int n, int k) {
result.clear();
path.clear();
backtracking(n, k, 1);
return result;
}
};