代码随想录算法训练营第二十六天| 39. 组合总和 40.组合总和II 131.分割回文串

组合总和

题目链接:39. 组合总和 - 力扣(LeetCode)

思路:依然一是套用回溯模板,但是我们这里用回溯的是i而不是i+1,因为元素可以重复使用,注意for循环里if(sum(path)<=target)的等号不能少。

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    int sum(vector<int> path){
        int s=0;
        for(auto i:path)s+=i;
        return s;
    }
    void backtracking(vector<int> c,int target,int start){
        if(sum(path)==target){
            result.push_back(path);
        }else{
            for(int i=start;i<c.size();i++){
                path.push_back(c[i]);
                if(sum(path)<=target){
                backtracking(c,target,i);
                path.pop_back();
                }
                else{
                path.pop_back();
                }
            }
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates,target,0);
        return result;
    }
};

但事实上,我们可以利用递归特性来完成总和计算,额外写一个sum函数来计算总和会极大降低我们的效率,尽管思路一致,上下两种写法的速度差了很多。

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<int> state;              // 状态(子集)
        sort(candidates.begin(), candidates.end()); // 对 candidates 进行排序
        int start = 0;                  // 遍历起始点
        vector<vector<int>> res;        // 结果列表(子集列表)
        backtrack(state, target, candidates, start, res);
        return res;
    }
private:
    void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
        // 子集和等于 target 时,记录解
        if (target == 0) {
            res.push_back(state);
            return;
        }
        // 遍历所有选择
        // 剪枝二:从 start 开始遍历,避免生成重复子集
        for (int i = start; i < choices.size(); i++) {
            // 剪枝一:若子集和超过 target ,则直接结束循环
            // 这是因为数组已排序,后边元素更大,子集和一定超过 target
            if (target - choices[i] < 0) {
                break;
            }
            // 尝试:做出选择,更新 target, start
            state.push_back(choices[i]);
            // 进行下一轮选择
            backtrack(state, target - choices[i], choices, i, res);
            // 回退:撤销选择,恢复到之前的状态
            state.pop_back();
        }
    }
};

作者:Krahets
链接:https://leetcode.cn/problems/combination-sum/solutions/2363929/39-zu-he-zong-he-hui-su-qing-xi-tu-jie-b-9zx7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

组合总和II

题目链接:40. 组合总和 II - 力扣(LeetCode)

思路:本来以为是一个简单的回溯题,直到我遇到了

看来这道题对剪枝的要求不低啊。下面的做法就被这个用例卡了。

class Solution {
public:
    vector<vector<int>>result;
    vector<int>path;
    map<vector<int>,int>m;
    void backtracking(vector<int>candidates,int target,int start){
        if(target==0){
            if(!m[path]){
            result.push_back(path);
            ++m[path];
            }
        }else{
            for(int i=start;i<candidates.size();i++){
                path.push_back(candidates[i]);
                if(target-candidates[i]>=0)
                {backtracking(candidates,target-candidates[i],i+1);
                path.pop_back();
                }else{
                    path.pop_back();
                }
            }
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        int sum=0;
        for(auto i:candidates)sum+=i;
        if(sum<target)return result;        //剪枝
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0);
        return result;
    }
};

下面看一个剪枝剪得很巧妙的解法,该解法规避了对重复元素的反复递归,因为它将数组排序后,如果发现重复选取同一个数字,则直接结束本轮循环,但本题的答案中又有可能出现相同的元素。其实并不矛盾,因为这个解法里每一次递归后,start发生变化,即使元素重复,但第一个元素又能取到了,因此仍能完成任务。

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> state;              // 状态(子集)
        sort(candidates.begin(), candidates.end()); // 对 candidates 进行排序
        int start = 0;                  // 遍历起始点
        vector<vector<int>> res;        // 结果列表(子集列表)
        backtrack(state, target, candidates, start, res);
        return res;
    }
private:
    void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
        // 子集和等于 target 时,记录解
        if (target == 0) {
            res.push_back(state);
            return;
        }
        // 遍历所有选择
        // 剪枝二:从 start 开始遍历,避免生成重复子集
        // 剪枝三:从 start 开始遍历,避免重复选择同一元素
        for (int i = start; i < choices.size(); i++) {
            // 剪枝一:若子集和超过 target ,则直接结束循环
            // 这是因为数组已排序,后边元素更大,子集和一定超过 target
            if (target - choices[i] < 0) {
                break;
            }
            // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过
            if (i > start && choices[i] == choices[i - 1]) {
                continue;
            }
            // 尝试:做出选择,更新 target, start
            state.push_back(choices[i]);
            // 进行下一轮选择
            backtrack(state, target - choices[i], choices, i + 1, res);
            // 回退:撤销选择,恢复到之前的状态
            state.pop_back();
        }
    }
};

作者:Krahets
链接:https://leetcode.cn/problems/combination-sum-ii/solutions/2363941/40-zu-he-zong-he-iihui-su-qing-xi-tu-jie-7y8s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

分割回文串

题目链接:131. 分割回文串 - 力扣(LeetCode)

思路:本题投降代码随想录 (programmercarl.com)

class Solution {
private:
    vector<vector<string>> result;
    vector<string> path; // 放已经回文的子串
    void backtracking (const string& s, int startIndex) {
        // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
        if (startIndex >= s.size()) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isPalindrome(s, startIndex, i)) {   // 是回文子串
                // 获取[startIndex,i]在s中的子串
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
            } else {                                // 不是回文,跳过
                continue;
            }
            backtracking(s, i + 1); // 寻找i+1为起始位置的子串
            path.pop_back(); // 回溯过程,弹出本次已经添加的子串
        }
    }
    bool isPalindrome(const string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            if (s[i] != s[j]) {
                return false;
            }
        }
        return true;
    }
public:
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s, 0);
        return result;
    }
};

 

posted @ 2024-02-23 19:20  SandaiYoung  阅读(3)  评论(0编辑  收藏  举报