【Day26】回溯算法part3

系列文章目录



前言


39. 组合总和

Source: 题目
Note:非常有趣的一道题,剪枝很有趣,一定要先进行排序。思考这道题是如何进行去重的?

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为: [ [7], [2,2,3] ]

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
public:
    void backtracing(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) {
            // 思考如何结果去重? -自然去重,不会选到重复的组合 -通过for循环
            result.push_back(path);
            return;
        }else if (sum > target) {
            return;
        }

        // 第二部分条件是为了剪枝,若超过则不用进入下一次回溯,前提是要进行排序(前面一群小的数字已经加超过target,)
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            // 注意这里不是i+1,说明在下一层不损失可选数字的数量!!!
            // 这里是i而不是startindex,我们希望通过每次for循环来自然减少可选数字
            backtracing(candidates, target, sum, i);
            sum -= candidates[i];
            path.pop_back();
        }

    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        // 如果要进行剪枝 则一定要先排序,不排序会出错!
        // 因为此时sum + candidates[i]>target不能保证sum + candidates[i+1] > target
        sort(candidates.begin(), candidates.end());
        backtracing(candidates, target, 0, 0);
        return result;
    }
};

Tips:

40.组合总和II

Source: 题目
Note:题目给定一个数组和target,要求找到所有数组内元素累加与target相等的子数组,结果集不能重复。

这道题引入了used数组的概念,用于记录使用过的元素,原因是相等的元素可以在纵向遍历中重复使用,而不能在同层中使用(会引发重复的结果)。

另外就是要记住vector排序的方法

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;

public:
    void backtracing(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
        // 结束条件
        if (sum == target) {
            result.push_back(path);
            return;
        }else if (sum > target) {
            return;
        }

        // 条件中的剪枝操作,跳过无用的回溯过程(因为从小到大排序,所以可以这么做)
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {    
            // 注意这里是used[i - 1] == false , 为了避免越界 i > 0
            // 由于回溯是从上到下有点像dfs,所以纵向遍历的时候used为true,可以继续使用重复元素
            // 而同一层的时候used已经被重置为false,这时候不能再用这个元素,因为会出现重复结果组合
            // 这个条件需要仔细思考
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            used[i] = true;
            backtracing(candidates, target, sum, i + 1, used);
            sum -= candidates[i];
            used[i] = false;
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        // 需要一个vector记录 当前层是否能继续访问同元素
        // 我们不希望在同一层取到同样的数字,因为会导致结果集重复
        vector<bool> used(candidates.size(), false);
        // 这个sort()排序vector的用法也需要熟记
        sort(candidates.begin(), candidates.end());
        backtracing(candidates, target, 0, 0, used);
        return result;
    }
};

Tips:

131.分割回文串

Source: 题目
Note:题目给定一个字符串,要求返回所有的可能的分割,要求分割后的子字符串满足都是回文串
Example 1:
Input: s = “aab”
Output: [[“a”,“a”,“b”],[“aa”,“b”]]
Example 2:
Input: s = “a”
Output: [[“a”]]

class Solution {
private:
    vector<vector<string>> result;
    vector<string> path;
public:
    void backtracing(const string& s, int startIndex) {
        if (startIndex >= s.length()) {
            result.push_back(path);
            return;
        }
    
        for (int i = startIndex; i < s.length();i++) {
            // 如果从字符串的[startIndex, i]是回文串,那么可以进行回溯
            if (isP(s, startIndex, i)) {
                // 使用了 substr提取子字符串, 第二个参数为从startIndex 到 i (包含)的字符总数
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
                // i + 1 表示 下一层不重复切割已经切过的部分
                backtracing(s, i + 1);
                path.pop_back(); 
            }else {
                continue;
            }
        }
    }
    // 判断是否是回文串, 可以用动态规划
    bool isP(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;
    }

    vector<vector<string>> partition(string s) {
        backtracing(s, 0);
        return result;
    }
};

Tips:

总结

更加加深了回溯算法的理解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值