class Solution {
public:
vector<vector<int> >ans;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> res;
sort(candidates.begin(),candidates.end());
combination(res,candidates,0,0,target);
return ans;
}
void combination(vector<int> &res,vector<int> &candidates,int n,int sum,int target){
if(sum==target){
ans.push_back(res);
return;
}
for(int i=n;i<candidates.size();i++){
if(sum>target) break;
res.push_back(candidates[i]);
combination(res,candidates,i,sum+candidates[i],target);
res.pop_back();
}
}
};
代码分析与思路
1. combinationSum 函数
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> res;
sort(candidates.begin(), candidates.end()); // 将候选数值排序,以便在递归中早期剪枝
combination(res, candidates, 0, 0, target); // 调用递归函数
return ans; // 返回所有的组合结果
}
• candidates:一个数组,包含候选的数值。
• target:目标和,要求组合的和为该值。
• res:当前递归路径中存储的数字组合。
• ans:存储所有满足条件的数字组合的结果集。
步骤解释:
1. 首先对候选数值进行排序。排序的目的是为了方便剪枝。排序后,如果当前和已经超过目标,就可以直接剪枝,避免不必要的递归。
2. 然后调用递归函数 combination 来生成所有的组合。
2. combination 函数
void combination(vector<int>& res, vector<int>& candidates, int n, int sum, int target) {
if (sum == target) { // 如果当前的和等于目标,说明找到了一个有效的组合
ans.push_back(res); // 将当前组合添加到答案中
return;
}
for (int i = n; i < candidates.size(); i++) { // 从当前位置开始遍历候选数值
if (sum + candidates[i] > target) break; // 如果当前和已经超过目标,提前结束循环(剪枝)
res.push_back(candidates[i]); // 将当前数值添加到组合中
combination(res, candidates, i, sum + candidates[i], target); // 递归调用,注意这里 i 不变,允许重复使用当前数值
res.pop_back(); // 回溯,移除最后添加的数值
}
}
• res:当前递归路径下的数字组合。
• candidates:候选数值数组。
• n:递归时的当前索引,表示从哪个位置开始选择候选数值。
• sum:当前递归路径下的数值和。
• target:目标和。
步骤解释:
1. 递归终止条件:当 sum == target 时,说明当前组合的和已经等于目标和,将当前组合 res 添加到答案 ans 中。
2. 循环遍历候选数值:
• 通过 for (int i = n; i < candidates.size(); i++) 来遍历候选数值数组,从索引 n 开始。这样做允许重复使用同一个数字,因此递归函数的 i 是从当前位置开始的,而不是 i + 1。
• 剪枝:if (sum + candidates[i] > target) break; 如果当前和加上当前候选数值已经大于目标和 target,就不再继续递归,这就是剪枝操作,避免不必要的计算。
3. 回溯:在递归过程中,先将当前数字添加到 res 中,递归调用 combination,回溯时通过 res.pop_back() 移除上一个添加的数字,以便继续尝试其他组合。
好的,我们再进一步细化每一步,详细描述每次递归、回溯的操作,以及如何从根本上理解每个调用。
我们继续使用 candidates = [2, 3, 6, 7] 和 target = 7 作为例子。
初始化
combinationSum([2, 3, 6, 7], 7)
1. 排序候选数组:
• 首先对 candidates 进行排序,得到 [2, 3, 6, 7]。虽然在这个例子中排序后的顺序和原顺序一样,但排序能帮助我们更高效地剪枝。排序的作用是:如果当前和已经大于目标值,剩下的数字就不需要再尝试。
2. 递归调用:
• 调用 combination(res, candidates, 0, 0, 7),开始从候选数值的第一个数字开始递归,res 是当前的组合,sum 是当前组合的和,n 是当前递归的索引,target 是目标和。
好的,让我们继续详细展示后面的所有递归步骤,以确保全面覆盖所有细节。
继续递归步骤分析
我们继续从之前的位置开始,每一步都详细地解释,直到完成所有可能的组合。
回到第一次递归 combination(res, candidates, 0, 0, 7)
• **当前组合 res**: []`(空组合)
• 当前和 sum: 0
• 目标值 target: 7
选择位置从 n = 0 开始,尝试候选数组中的每个元素。
选择 2(第一个候选数字):
• 当前组合变为 res = [2]
• 当前和 sum 变为 0 + 2 = 2
• 递归调用:combination([2], [2, 3, 6, 7], 0, 2, 7)。
第二次递归调用 combination(res, candidates, 0, 2, 7)
• 当前组合 res: [2]
• 当前和 sum: 2
• 目标值 target: 7
我们继续从 n = 0 尝试候选数组中的每个元素。
选择 2(重复选择):
• 当前组合变为 res = [2, 2]
• 当前和 sum 变为 2 + 2 = 4
• 递归调用:combination([2, 2], [2, 3, 6, 7], 0, 4, 7)。
第三次递归调用 combination(res, candidates, 0, 4, 7)
• 当前组合 res: [2, 2]
• 当前和 sum: 4
• 目标值 target: 7
我们继续从 n = 0 尝试候选数组中的每个元素。
选择 2(重复选择):
• 当前组合变为 res = [2, 2, 2]
• 当前和 sum 变为 4 + 2 = 6
• 递归调用:combination([2, 2, 2], [2, 3, 6, 7], 0, 6, 7)。
第四次递归调用 combination(res, candidates, 0, 6, 7)
• 当前组合 res: [2, 2, 2]
• 当前和 sum: 6
• 目标值 target: 7
继续从 n = 0 尝试候选数组中的每个元素。
选择 2(重复选择):
• 当前组合变为 res = [2, 2, 2, 2]
• 当前和 sum 变为 6 + 2 = 8(超出目标值 7)
剪枝:当前和超过了目标值 7,需要停止继续递归,进行回溯。
• 回溯:移除最后添加的 2,恢复为 res = [2, 2, 2]。
回溯到第三次递归 combination(res, candidates, 0, 4, 7)
• 恢复组合 res = [2, 2],当前和 sum = 4。
继续尝试下一个候选数字。
选择 3(下一个候选数字):
• 当前组合变为 res = [2, 2, 3]
• 当前和 sum 变为 4 + 3 = 7(等于目标值 7)
找到一个有效组合,将当前组合 [2, 2, 3] 添加到结果集 ans 中。
• 结果集 ans = [[2, 2, 3]]
回溯:移除 3,恢复为 res = [2, 2]。
回溯到第二次递归 combination(res, candidates, 0, 2, 7)
• 恢复组合 res = [2],当前和 sum = 2。
继续尝试下一个候选数字。
选择 3(下一个候选数字):
• 当前组合变为 res = [2, 3]
• 当前和 sum 变为 2 + 3 = 5
• 递归调用:combination([2, 3], [2, 3, 6, 7], 1, 5, 7)。
第五次递归调用 combination(res, candidates, 1, 5, 7)
• 当前组合 res: [2, 3]
• 当前和 sum: 5
• 目标值 target: 7
我们继续从 n = 1 尝试候选数组中的每个元素。
选择 3(重复选择):
• 当前组合变为 res = [2, 3, 3]
• 当前和 sum 变为 5 + 3 = 8(超出目标值 7)
剪枝:当前和超过了目标值 7,需要停止继续递归,进行回溯。
• 回溯:移除最后添加的 3,恢复为 res = [2, 3]。
选择 6(下一个候选数字):
• 当前组合变为 res = [2, 3, 6]
• 当前和 sum 变为 5 + 6 = 11(超出目标值 7)
剪枝:当前和超过了目标值 7,需要停止继续递归,进行回溯。
• 回溯:移除最后添加的 6,恢复为 res = [2, 3]。
选择 7(下一个候选数字):
• 当前组合变为 res = [2, 3, 7]
• 当前和 sum 变为 5 + 7 = 12(超出目标值 7)
剪枝:当前和超过了目标值 7,需要停止继续递归,进行回溯。
• 回溯:移除最后添加的 7,恢复为 res = [2, 3]。
回溯到第一次递归 combination(res, candidates, 0, 0, 7)
• 恢复组合 res = [2],当前和 sum = 2。
继续尝试下一个候选数字。
选择 6(下一个候选数字):
• 当前组合变为 res = [2, 6]
• 当前和 sum 变为 2 + 6 = 8(超出目标值 7)
剪枝:当前和超过了目标值 7,需要停止继续递归,进行回溯。
• 回溯:移除最后添加的 6,恢复为 res = [2]。
选择 7(下一个候选数字):
• 当前组合变为 res = [2, 7]
• 当前和 sum 变为 2 + 7 = 9(超出目标值 7)
剪枝:当前和超过了目标值 7,需要停止继续递归,进行回溯。
• 回溯:移除最后添加的 7,恢复为 res = [2]。
最终结果
通过递归和回溯的过程,我们最终得到的有效组合是:
• [2, 2, 3]
• [7]
返回结果:[[2, 2, 3], [7]]
总结
回溯法的步骤可以分为以下几个关键点:
1. 递归选择:从候选数组中选择一个数字并添加到当前组合。
2. 剪枝:如果当前和已经超过目标值,就停止继续递归,避免不必要的计算。
3. 回溯:当递归完成时,撤销最近的选择,尝试其他可能的数字。