2023.7.17
今天正式开始回溯系列,这是一道经典回溯题。 先上一个经典回溯模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
本题在模板的基础上,还要加上一个参数start,用于防止在集合中选到同样的元素,如[1,1][2,2]等。 下面细节看代码:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void backtracking(int n, int k, int start)
{
if(path.size() == k)
{
ans.push_back(path);
return;
}
for(int i=start; i<=n; i++)
{
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k)
{
backtracking(n,k,1);
return ans;
}
};
剪枝优化:
考虑一种极端情况:当n和k都取4时,那么当{1,2,3,4}添加到ans之后,从元素2开始遍历就没有意义了,因为需要4个元素,而从2开始遍历最多只有3个元素。 所以语句 for(int i=start; i<=n; i++)可以做一个优化:
- 我们需要的元素个数为:k-path.size()
- 集合剩余元素为:n-i+1 (包括初始元素i,所以要+1)
- 我们需要的元素应该小于等于集合剩余元素:k-path.size() <= n-i+1,反解得到: i <= n-k+path.size()+1
优化后的代码为:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void backtracking(int n, int k, int start)
{
//中止条件
if(path.size() == k)
{
ans.push_back(path);
return;
}
//for(int i=start; i<=n; i++)
for(int i=start; i<=n-k+path.size()+1; i++)
{
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k)
{
backtracking(n,k,1);
return ans;
}
};