LeetCodehot 力扣热题100 组合总和

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. 回溯:当递归完成时,撤销最近的选择,尝试其他可能的数字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值