leetcode 今天你学会回溯了么(39、40、216、377 组合总和1234 回溯/动态规划)

39 组合总和

题目描述: 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明: 所有数字(包括 target)都是正整数;解集不能包含重复的组合。
思路: 回溯 + 剪枝。由于candidates数组中不存在重复元素,则不需判断解集中是否包含重复的组合。

class Solution {
public:
    void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& tmp, int sum, int pos) {
        if(pos == candidates.size()) {
            if(sum == target) {
                ans.push_back(tmp);
                // mp[tmp] = 1;
            }
            return ;
        }
        for(int i = 0; i <= (target - sum) / candidates[pos]; i++) { // i表示取在pos位置的数的次数,0次为不取, 最多取 (target - sum) / candidates[pos]次。
            if(i == 0) // 不取candidates[pos]这个数,不需要回溯,直接考虑下一个数
                dfs(candidates, target, ans, tmp, sum, pos + 1);
            else {
                for(int j = 0; j < i; j++) { // 取i次candidates[pos]这个数,需要对压入和加和重复i次。
                    tmp.push_back(candidates[pos]);
                    sum += candidates[pos];
                }
                dfs(candidates, target, ans, tmp, sum, pos + 1);
                // 回溯 回到没有对candidates[pos]这个数进行操作的状态。
                for(int j = 0; j < i; j++) {
                    tmp.pop_back();
                    sum -= candidates[pos];
                }
            }
        }
        return ;
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        // sort(candidates.begin(), candidates.end());
        vector<vector<int>> ans;
        vector<int> tmp;
        // map<vector<int>, int> mp;
        int sum = 0;
        dfs(candidates, target, ans, tmp, sum, 0);
        return ans;
    }
};

40 组合总和2

题目描述: 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数;解集不能包含重复的组合。
思路1: 和39题的区别在于数组candidates中可能存在重复元素。并且candidates中的每个数字在每个组合中只能使用一次。即该题与39题的差别为如何避免重复。 在本题中,用map避免重复,并进行多次剪枝(见注释)。

class Solution {
public:
    void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& tmp, map<vector<int>, int>& mp, int sum, int pos) {
        if(sum > target) // 剪枝
            return ;
        if(sum == target) { // 剪枝
            if(mp[tmp] == 0) {
                ans.push_back(tmp);
                mp[tmp] = 1;
            }
            return ;
        }
        if(pos == candidates.size()) {
            if(sum == target && mp[tmp] == 0) {
                ans.push_back(tmp);
                mp[tmp] = 1;
            }
            return ;
        }
        dfs(candidates, target, ans, tmp, mp, sum, pos + 1);
        sum += candidates[pos];
        tmp.push_back(candidates[pos]);
        dfs(candidates, target, ans, tmp, mp, sum, pos + 1);
        sum -= candidates[pos];
        tmp.pop_back();
        return ;
    }

    static bool cmp(int a, int b) {
        return a > b;
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end(), cmp); // 排序去重 从大到小排序 方便后续剪枝
        vector<vector<int>> ans;
        vector<int> tmp;
        map<vector<int>, int> mp;
        int sum = 0;
        int start = 0;
        for(int i = 0; i < candidates.size(); i++) { // 剪枝 选择起始点 
            if(candidates[i] <= target) {
                start = i;
                break;
            }
        }
        dfs(candidates, target, ans, tmp, mp, sum, start);
        return ans;
    }
};

思路2: 用map去重时,由于重复数目太多,搜索空间比较大,耗时高。我们可以提前遍历candidates数组,统计其中每个元素的出现次数。按照每个元素出现0次->最高次数回溯枚举。这样就不会产生重复答案。

class node {
    public:
        int num;
        int cnt;
};

class Solution {
public:
    void dfs(int target, vector<vector<int>>& ans, vector<int>& tmp, vector<node>& store, int sum, int pos) {
        if(sum > target)
            return ;
        if(sum == target) {
            ans.push_back(tmp);
            return ;
        }
        if(pos == store.size())
            return ;
        for(int i = 0; i <= store[pos].cnt; i++) {
            if(i != 0) {
                sum += store[pos].num;
                tmp.push_back(store[pos].num);
            }
            dfs(target, ans, tmp, store, sum, pos + 1);
        }
        for(int i = 1; i <= store[pos].cnt; i++) {
            sum -= store[pos].num;
            tmp.pop_back();
        }
        return ;
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector<vector<int>> ans;
        vector<int> tmp;
        // 统计candidates数组中每个数字出现的次数。统计前需要对candidates数组排序。
        vector<node> store;
        int pre_sum = candidates[0];
        int cnt = 1;
        for(int i = 1; i < candidates.size(); i++) {
            if(candidates[i] == pre_sum)
                cnt++;
            else {
                store.push_back(node{pre_sum, cnt});
                pre_sum = candidates[i];
                cnt = 1;
            }
        }
        if(cnt > 0) 
            store.push_back(node{pre_sum, cnt});
        // for(int i = 0; i < store.size(); i++) {
        //     cout<<store[i].num<< " "<< store[i].cnt<<endl;
        // }
        int sum = 0;
        dfs(target, ans, tmp, store, sum, 0);
        return ans;
    }
};

思路3: 如何去掉一个数组中重复的元素,除了使用哈希表以外,我们还可以先对数组升序排序,重复的元素一定不是排好序以后的第 1 个元素和相同元素的第 1 个元素。这个方法最重要的作用是,可以让同一层级,不出现相同的元素。但是却允许了不同层级之间的重复。
在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。第一个出现的2的特点就是 cur == begin。 第二个出现的2 特点是cur > begin。

class Solution {
public:
    void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& tmp, int sum, int pos) {
        if(sum > target)
            return ;
        if(sum == target) {
            ans.push_back(tmp);
            return ;
        }  
        if(pos == candidates.size())
            return ;
        for(int i = pos; i < candidates.size(); i++) {
            if(i > pos && candidates[i] == candidates[i - 1])
                continue;
            sum += candidates[i];
            tmp.push_back(candidates[i]);
            dfs(candidates, target, ans, tmp, sum, i + 1);
            sum -= candidates[i];
            tmp.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector<vector<int>> ans;
        vector<int> tmp;
        int sum = 0;
        dfs(candidates, target, ans, tmp, sum, 0);
        return ans;
    }
};

216 组合总和3

题目描述: 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明: 所有数字都是正整数。解集不能包含重复的组合。
思路: dfs回溯。1-9九层dfs,每层确定当前数字取或者不取。最终判断取到的数字个数是否为k个。

class Solution {
public:
    // 和为n k个数
    void dfs(int k, int n, vector<vector<int>>& ans, vector<int>& tmp, int sum, int num, int cnt) {
        if(num == 10) {
            if(cnt == k && sum == n) { // 有组成个数的限制
                ans.push_back(tmp);
            }
            return ;
        }
        dfs(k, n, ans, tmp, sum, num + 1, cnt);
        sum += num;
        tmp.push_back(num);
        cnt++;
        dfs(k, n, ans, tmp, sum, num + 1, cnt);
        sum -= num;
        tmp.pop_back();
        cnt--;
        return ;
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> ans;
        vector<int> tmp;
        int sum = 0;
        dfs(k, n, ans, tmp, sum, 1, 0);
        return ans;
    }
};

377 组合总和4

题目描述: 给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
思路1: DFS回溯会超时。
思路2: 由于不需要得出具体的组合表示,因此DP可以是求解本题的方法。首先求出数组中所有元素的最大公约数,用target除以最大公约数,如果模为0,则存在所求组合方案,否则返回0。

class Solution {
public:
    // 动态规划
    int combinationSum4(vector<int>& nums, int target) {
        if(nums.size() == 0)
            return 0;

        int gcd = nums[0];
        for(int i = 0; i < nums.size(); i++) {
            gcd = __gcd(gcd, nums[i]);
        }
        if(target % gcd != 0)
            return 0;

        vector<unsigned long long> dp(target + 1, 0);
        dp[0] = 1;
        for(int i = 0; i < dp.size(); i++) {
            for(int j = 0; j < nums.size(); j++) {
                if(i - nums[j] >= 0)
                    dp[i] += dp[i - nums[j]];
            }
        }
        // for(int i = 1; i < dp.size(); i++) {
        //     cout<<dp[i]<<endl;
        // }
        return (int)dp[target];
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值