代码随想录训练营Day 29|力扣39. 组合总和、40.组合总和II、131.分割回文串

代码随想录训练营Day 29|力扣39. 组合总和、40.组合总和II、131.分割回文串

码农世界 2024-05-15 前端 58 次浏览 0个评论

1.组合总和

题目链接/文章讲解: 代码随想录

视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili

代码:(未剪枝版 )

class Solution {
private:
    vector path;
    vector> result;
    void backtracking(vector& candidates,int target,int startIndex){
        // 递归出口
        if(target < 0){
            return;
        }
        if(target == 0){
            result.push_back(path);
            return;
        }
        // 处理结点
        for(int i = startIndex;i < candidates.size();i++){
            target -= candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,i);
            path.pop_back();
            target += candidates[i];
        }
    }
public:
    vector> combinationSum(vector& candidates, int target) {
        if(candidates.size() == 0) return result;
        backtracking(candidates,target,0);
        return result;
    }
};

 代码:(剪枝版)

class Solution {
private:
    vector path;
    vector> result;
    void backtracking(vector& candidates,int target,int startIndex){
        // 递归出口
        if(target < 0){
            return;
        }
        if(target == 0){
            result.push_back(path);
            return;
        }
        // 处理结点
        // 这里加了判断:如果下一轮的target已经小于0了,就没有必要递归了
        for(int i = startIndex;i < candidates.size() && target - candidates[i] >= 0;i++){
            target -= candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,i);
            path.pop_back();
            target += candidates[i];
        }
    }
public:
    vector> combinationSum(vector& candidates, int target) {
        if(candidates.size() == 0) return result;
        sort(candidates.begin(), candidates.end()); // 涉及大小的剪枝必排序
        backtracking(candidates,target,0);
        return result;
    }
};

思路:学到了这种元素数量不限制的取的解法

其实startIndex变量还是要有,因为我们求的是组合,要数层去重;并且这是在同一个数组里取数。

而数量不限,体现在我在递归里传入的starIndex的值是i,而不是i+1了。之前说过for循环管控每一层数的宽度;递归则管控数的深度。这样更改后,就可以在取完一个数的情况后,继续取这个数。实现如下图所示的效果:

关于剪枝的操作。这种涉及到总和大小的取值时。剪枝就要将给定的数组排序! 而且要注意给定的数组元素里全是正数,只有正数才越加越大。

这道题,我直接用目标总和target取减去每一个结点的数值了,看最后是否被减为0了。

2.组合总和2 

 题目链接/文章讲解:代码随想录

视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili

 代码:去重且剪枝过的

class Solution {
private:
    vector path;
    vector> result;
    void backtracking(vector& candidates,int target,int startIndex,vector& used){
        if(target < 0){
            return;
        }
        if(target == 0){
            result.push_back(path);
            return;
        }
        for(int i = startIndex;i < candidates.size() && target - candidates[i] >= 0;i++){
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 要对同一树层使用过的元素进行跳过
            if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){
                continue;
            } // 数层去重
            used[i] = true;
            target -= candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,i + 1,used);
            path.pop_back();
            target += candidates[i];
            used[i] = false;
        }
    }
public:
    vector> combinationSum2(vector& candidates, int target) {
        vector used(candidates.size(),false);
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,used);
        return result;
    }
};

思路:

这道题和上题的区别是:

每个元素只能使用一次——startIndex为i+1;

给定的元素有重复值,但是要去重后的结果:这里就是难点。首先去重是有两个维度的。

 一个组合里可以有相同大小的不同元素,所以递归操作里不需要去重,即上图的同一个子树的数枝上不需要去重;不同组合不能相同,所以要在数层去重。

这里用一个数组used来记录,遇到的和前一个元素相同的情况(这里去重先去把给定数组排序)时,到底是和前一个相同元素是在同一个树枝里的,还是同一个树层里的。

在同一个树枝里:前一个元素已经加入到了path里,已经使用过了。

在同一个树层里:前一个元素没有加入到path里,没有被使用,这是独立的两个分支。

所以如果遇到这种树层里重复的情况,加个判断语句,直接continue(因为接下来的情况还有我们要收集的,所以只是跳过这一种情况,用continue)

3.分割回文串

代码随想录

视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!_哔哩哔哩_bilibili

 代码:

class Solution {
private:
    vector path;
    vector> result;
    bool isPalindrome(string& s,int start,int end){
        while(start <= end){
            if(s[start] != s[end]){
                return false;
            }
            start++;
            end--;
        }
        return true;
    }
    void backtracking(string& s,int startIndex){
        // 递归出口
        if(startIndex >= s.size()){
            result.push_back(path);
            return;
        }
        // 遍历结点
        // 回文子串的范围是[startIndex,i]
        for(int i = startIndex;i < s.size();i++){
            // 在这里直接判断是否子串满足回文子串
            if(isPalindrome(s,startIndex,i)){
                string str = s.substr(startIndex,i - startIndex + 1);
                path.push_back(str);
            }else{
                continue;
            }
            backtracking(s,i + 1);
            path.pop_back();
        }
    }
public:
    vector> partition(string s) {
        if(s.size() == 0) return result;
        backtracking(s,0);
        return result;
    }
};

思路:

关于substr函数:

这道题很巧妙地将startIndex作为了子串的开始下标,子串的范围可以表示为[startIndex,i]。

同时,本题将判断条件放在了for循环的里面来判断,来决定是否要执行下面的递归回溯操作。判断用了一个额外的函数来判断回文子串。 

细节问题其实就这些。

转载请注明来自码农世界,本文标题:《代码随想录训练营Day 29|力扣39. 组合总和、40.组合总和II、131.分割回文串》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,58人围观)参与讨论

还没有评论,来说两句吧...

Top