77. 组合 - 力扣(LeetCode)
题目描述
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
样例输入
示例 1:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
示例 2:
输入:n = 1, k = 1 输出:[[1]]
提示:
1 <= n <= 20
1 <= k <= n
题解
暴力算法
int n = 4;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
cout << i << " " << j << endl;
}
}
在上述暴力算法中,题目中k等于多少,我们就要嵌套多少个for循环,显然这样写代码是不合理的,而在回溯算法中,我们用递归代替嵌套的for循环
回溯算法
核心
- for循环的本质是遍历每一层
- 递归的本质是遍历每个深度下的树枝
核心代码:
//横向遍历
for(int i=startIndex;i<=n;i++)
{
path.emplace_back(i);//处理节点
backing(n,k,i+1,path,res);//纵向遍历
path.pop_back();//回溯
}
在上述代码中,我们用for循环用来横向遍历,递归的过程是纵向遍历。同时用startIndex控制每层遍历的起始位置,每往深层下降一层就用path保存取到的节点i,当满足终止条件return返回到上一层前要进行回溯,撤销处理的结点。
也就是说,backing(递归函数)通过不断调用自己一直往深处遍历,总会遇到叶子节点,遇到了叶子节点就要返回。
那么终止条件是什么呢?很显然,每当我们收集path的过程中path的大小等于k的时候,就说明我们已经收集到了一个满足题意的结果,此时即可终止本次递归,返回上一层,即:
//递归出口
if(path.size()==k){
res.push_back(path);//收集结果
return;
}
代码
class Solution {
public:
void backing(int& n,int& k,int startIndex,vector<int>& path,vector<vector<int>>& res)
{
//递归出口
if(path.size()==k){
res.push_back(path);//收集结果
return;
}
//横向遍历,n-(k-path.size())+1为剪枝优化
for(int i=startIndex;i<=n-(k-path.size())+1;i++)
{
path.emplace_back(i);
backing(n,k,i+1,path,res);//纵向遍历
path.pop_back();//回溯
}
}
vector<vector<int>> combine(int n, int k) {
vector<int> path;
vector<vector<int>> res;
backing(n,k,1,path,res);
return res;
}
};