目录
- 1、题目
- 2、回溯法思路
- 3、参考其他思路,更深入了解这个问题
- 4、剪枝优化
可能需要回顾到的知识文章:
1、常用算法总结(穷举法、贪心算法、递归与分治算法、回溯算法、数值概率算法)
2、回溯法初步
删除vector容器中的对象元素的三种方法:pop_back, erase与remove算法
1、题目
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
2、回溯法思路
集合元素个数为n,由此可以看出解空间树每个结点有n孩子,深度为k。
利用模板,就就就AC了。。。
每次确定一个起点和一个终点,从start遍历到end(对于本层元素而言)。
将第i个元素放入res中,然后进入下一层,下一层的起点是i+1(这样start只会一直向大的数值延伸,不会产生重复的元素)。
如果不符合就把这个元素pop掉。
class Solution {
public:vector<vector<int>> result;vector<int> res;void backtracking(int start,int end,int k){//找到了k个数if(res.size() == k){result.push_back(res);return;}for(int i=start;i<=end;i++){//处理结点;res.push_back(i);//递归,探索下一层backtracking(i+1,end,k); //递归//回溯,撤销处理结果res.pop_back();}}vector<vector<int>> combine(int n, int k) {result.clear();res.clear();backtracking(1,n,k);return result;}
};
3、参考其他思路,更深入了解这个问题
对于较小的k,我们可以很容易想到for循环嵌套k层解决,如下:
n=4,k=2;
int n = 4;
for (int i = 1; i <= n; i++) {for (int j = i + 1; j <= n; j++) {cout << i << " " << j << endl;}
}
但是对于较大的k,我们的显然不能写嵌套for。例如要解决n=100.k=50的情况,暴力写法需要嵌套50层for循环,而回溯法就是利用递归来解决嵌套层数的问题。
每一层递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了。
观察我们的解空间树:
每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围。
n相当于树的宽度,k相当于树的深度。
4、剪枝优化
下面的遍历范围是可以剪枝优化的:
for(int i=start;i<=end;i++)
{//处理结点;res.push_back(i);//递归,探索下一层backtracking(i+1,end,k); //递归//回溯,撤销处理结果res.pop_back();
}
举例n=4,k=4时,那么第一层for循环的时候,从元素2开始的遍历就已经没有意义了,因为遍历了也凑不到4个元素了。
所以我们可以剪枝的地方就是每一层的end。
我们已经选择的元素个数为:res.size()
我们还需要的元素的个数为k-res.size()
所以最多从end-(k-res.size())+1
的地方开始遍历。
所以可以修改为:
for(int i=start;i<=end-(k-res.size())+1;i++)
{//处理结点;res.push_back(i);//递归,探索下一层backtracking(i+1,end,k); //递归//回溯,撤销处理结果res.pop_back();
}
完整代码:
class Solution {
public:vector<vector<int>> result;vector<int> res;void backtracking(int start,int end,int k){//找到了k个数if(res.size() == k){result.push_back(res);return;}for(int i=start;i<=end-(k-res.size())+1;i++){//处理结点;res.push_back(i);//递归,探索下一层backtracking(i+1,end,k); //递归//回溯,撤销处理结果res.pop_back();}}vector<vector<int>> combine(int n, int k) {result.clear();res.clear();backtracking(1,n,k);return result;}
};
可以看到速度是有明显的提升的: