题目来自leetcode 77题
给定两个整数 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]]
组合问题的思考
回溯算法的步骤:确定返回值与参数-确定递归函数-确定单层递归-回溯(关键)
确定返回值与参数
首先设定两个全局变量,result 和 path:
vector<vector<int>> result;
vector<int> path; //result是存储所有path组合的,path存储每一次取出的组合;
然后将条件中要用到的输入n值与k值,和另外再假设一个值startIndex,记录取出的一个数值,作为参数。
void backtracking(int n,int k,int startIndex);
递归函数与终止条件
void backtracking(int n ,int k ,int startIndex)
把这次回溯当作是一棵树,那么访问到叶子节点,也就是当path的大小刚好是k值,那么就可以结束一次取值;
if(path.size() == k) {
result.push_back(path);
return;}
单层循环
递归函数适用于纵向,往下遍历,for循环适用于横向循环;
for(int i = startIndex;i <=n ;i++) {
path.push_back(i);//处理节点
backtracking(n,k,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;
}
};
剪枝优化
个人觉得,回溯算法上的剪枝优化,主要还是针对横向遍历时候的条件范围。
就是对于变量i在for循环中选择的起始位置:
for (int i = startIndex; i <= n; i++)
- 已经选择的元素个数:path.size()
- 还需要的元素个数:k-path.size()
- 在集合n中至多要从该起始位置(n-(k-path.size()+1))开始遍历
所有优化之后的代码主要如下:
for(int i = startIndex; i <= n - (k-path.size()+1);i++)
小结
组合问题当中,使用回溯算法三部曲,其实也是递归三步法,只不过在递归的过程中,多了回溯的一步;
同时要注意,对于题目中的剪枝优化,主要着手的地方就是横向遍历的条件。