代码随想录DAY23 - 回溯算法 - 08/22

组合总和

题干

题目:给你一个 无重复元素整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

说明:

  • 2 <= candidates[i] <= 40,数组中都是正数

  • 解集不能包含重复的组合

链接:. - 力扣(LeetCode)

思路和代码

同样是组合,但这道题不限制组合的大小,也不限制同一数字的使用次数。之前在做组合题目时,每用一个数字,下一个组合就不能用之前用过的,但在这道题里,每考虑一个新组合时本身这个数字能再次使用,这就是在原组合题目的基础上要修改的地方。

递归法
  • 递归参数和返回值:参数是目标和 target、组合之和 sum、传入的集合 candidates、起始的元素位置 startIndex;无返回值。

  • 递归结束的条件:当组合之和 sum 大于等于 目标和target时就要返回。

  • 递归顺序:先确定组合中的一个元素,再递归寻找下一个元素凑组合。但和之前不同的是,我们递归寻找下一个元素时还可以是自身,因为可以重复,因为递归时候传入的起始位置仍为 i,即 backTracking(target, sum, candidates, i),而不是 i+1。

class Solution {
public:
    vector<int> composition; // 记录组合
    vector<vector<int>> result; // 记录所有组合的结果
    void backTracking(int target, int &sum, vector<int>& candidates, int startIndex){
        if (sum == target){ // sum 记录组合之和
            result.push_back(composition);
            return;
        } else if (sum > target){
            return;
        }
        for (int i = startIndex; i < candidates.size(); ++i) {
            int num = candidates[i];
            composition.push_back(num);
            sum += num;
            backTracking(target,sum,candidates,i); // 这里输入应该是 i,而不是 i+1,因为元素可以重复
            composition.pop_back();
            sum -= num;
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        int sum = 0;
        backTracking(target,sum,candidates,0);
        return result;
    }
};
递归优化:剪枝

优化:对数组排序并减少不必要的 for 循环

数组 candidates 本身是无序的,因此我们在遍历到 sum > target 时返回后,又需要继续遍历下一个元素。但如果数组是升序排列,当 sum > target 时,也不需要往下再遍历了,因为之后的数组元素只会更大,不可能再出现 sum = target 的情况,可以直接终止循环。

class Solution {
public:
    vector<int> composition;
    vector<vector<int>> result;
    void backTracking(int target, int &sum, vector<int>& candidates, int startIndex){
        if (sum == target){
            result.push_back(composition);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum+candidates[i] <= target; ++i) {
        											// 剪枝操作,如果加入当前元素已经 > target 直接终止循环
            int num = candidates[i];
            composition.push_back(num);
            sum += num;
            backTracking(target,sum,candidates,i); 
            composition.pop_back();
            sum -= num;
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        int sum = 0;
        sort(candidates.begin(),candidates.end()); // 升序排列
        backTracking(target,sum,candidates,0);
        return result;
    }
};

组合总和Ⅱ

题干

题目:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

说明:

  • 1 <= candidates[i] <= 50,数组中只有正数

  • 数组中可能有重复元素

链接:. - 力扣(LeetCode)

思路和代码

本题和上一题不一样的地方就在于数组 candidates 中可能包含重复的数字,而且每个数字在一次组合中只能使用一次,不可以多次使用。由于有重复的数字,这样递归求得的组合中可能会重复,因此我们需要先对 candidates 去重,重复出现的数字其实表明了该数字在组合中可以用多少次,我们需要记录下不同数字的使用次数。

递归法
class Solution {
public:
    int count[51]; // 记录不同数字的使用次数, 1 <= candidates[i] <= 50
    vector<int> composition; // 记录组合
    vector<vector<int>> result; // 记录所有组合的结果
    void backTracking(vector<int>& candidates, int target, int sum, int startIndex){
        if (sum == target){
            result.push_back(composition);
            return;
        } else if (sum > target){
            return;
        }
        for (int i = startIndex; i < candidates.size(); ++i) {
            int num = candidates[i];
            sum += num;
            composition.push_back(num);
            count[num]--; // 此时数字 num 已被使用一次,故次数减一
            if (count[num] > 0) backTracking(candidates,target,sum,i); // 若还有使用次数,则重复使用数字
            else backTracking(candidates,target,sum,i+1); // 若没有使用次数,则使用下一个数字
            composition.pop_back();
            sum -= num;
            count[num]++;
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> newCandidates; // 记录去重后的新数组
        for (int i : candidates) {
            if (count[i] == 0){
                newCandidates.push_back(i);
            }
            count[i]++; // 记录下每个数字的使用次数
        }
        backTracking(newCandidates,target,0,0);
        return result;
    }
};
递归优化:剪枝

同样是对 candidates 数组进行升序排序,这样当 sum > target 时无需再进入循环。

class Solution {
public:
    int count[51]; // 1 <= candidates[i] <= 50
    vector<int> composition;
    vector<vector<int>> result;
    void backTracking(vector<int>& candidates, int target, int sum, int startIndex){
        if (sum == target){
            result.push_back(composition);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ++i) {
            										// 剪枝,减少循环
            int num = candidates[i];
            sum += num;
            composition.push_back(num);
            count[num]--;
            if (count[num] > 0) backTracking(candidates,target,sum,i);
            else backTracking(candidates,target,sum,i+1);
            composition.pop_back();
            sum -= num;
            count[num]++;
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> newCandidates; // 记录去重后的新数组
        for (int i : candidates) {
            if (count[i] == 0){
                newCandidates.push_back(i);
            }
            count[i]++; // 记录下每个数字的使用次数
        }
        sort(newCandidates.begin(),newCandidates.end()); // 排序
        backTracking(newCandidates,target,0,0);
        return result;
    }
};

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值