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

39. 组合总和

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

  • 一开始犯的错误:
    没有设置startIndex。导致得到了重复的组合!
    虽然题目允许重复数字,但是为了不得到重复的组合,还是需要限制每次for循环的startIndex!

  • 通过代码:

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

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        int sum = 0;
        backtracking(target,candidates,sum,0);
        return result;
    }

    void backtracking(int target, vector<int> candidates, int sum, int startIndex){
        if(sum == target){
            result.push_back(path);
            return;
        }
        else if(sum > target){
            return;
        }

        for(int i = startIndex;i < candidates.size();i++){
            path.push_back(candidates[i]);
            backtracking(target,candidates,sum + candidates[i],i);
            path.pop_back();
        }
        return;
    }
};

二、学习文章后收获

1.startIndex

什么时候需要用?

  • 如果是一个集合来求组合的话,就需要startIndex
    • 元素可重复,startIndex = i
    • 元素不可重复,startIndex = i + 1
  • 如果是多个集合求组合的话,不需要。

2.剪枝

如果当前的sum加上这层for循环会加上的candidate[i],已经大于target,则该层循环直接跳过。

// 如果 sum + candidates[i] > target 就终止遍历
 for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
 ...
 }

3.复杂度

时间复杂度: O(n * 2^n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此

空间复杂度: O(target)

why???


40. 组合总和 II

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

不知道怎么去重。
犯了经典错误,确实达成了一个元素只用一次,但是出现了重复的集合。

二、学习文章后收获

1两种去重

  • 树层去重:树层(宽度)维度上的去重。本题中,排序后,相同元素只需回溯搜索第一个,因为后面都是重复的。
  • 树枝去重:树枝(深度)维度上的去重。本题中,相同的元素也可以出现在同一集合里,所以不需要树枝方向的去重。

2.回溯三要素分析

  • 函数参数:candidates,target,startIndex(因为是同一集合内的组合,需要表明for循环的开始位置),used数组(用于记录每个元素的使用情况),sum
  • 返回条件:sum == target(收集结果)或sum > target(直接返回)
  • 单层回溯的逻辑:for循环中,如果该元素与上个元素相同,而且上个元素未使用,则跳过当前元素(即剪枝去重);否则,正常回溯。
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        bool* used = new bool[candidates.size()];
        for(int i = 0;i < candidates.size();i++){
            used[i] = false;
        }
        backtracking(candidates,target,0,used,0);
        return result;
    }
    void backtracking(vector<int> candidates, int target, int startIndex, bool* used,int sum){
        if(sum == target){
            result.push_back(path);
            return;
        }
        else if(sum > target){
            return;
        }

        for(int i = startIndex;i < candidates.size();i++){
        	//如果该元素与上个元素相同,而且上个元素未使用,则跳过当前元素,即剪枝去重
            if(i >= 1 && candidates[i] == candidates[i-1] && used[i-1] == false){
                continue;
            }
            path.push_back(candidates[i]);
            used[i] = true;
            backtracking(candidates,target,i+1,used,sum+candidates[i]);
            path.pop_back();
            used[i] = false;
        }
        return;
    }
};

请你再读一遍文章~没读完


131.分割回文串

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

一开始没有思路,因为不知道怎么”有规律“地分割。之后看了文章里的图,明白了分割的规律在于for循环中逐步增加字串的大小。之后写出来了,通过。

在这里插入图片描述

回溯三要素的分析

  • 函数参数:字符串s,后续分割的起始位置(也就是剩下的字符串的起始位置)startIndex
  • 返回条件:startIndex == s.size(),说明整个字符串分割完了。
  • 处理回溯的逻辑:for循环中,逐步增大本层所分割子串的长度。如果是回文串,则加入path并进入下一层回溯。否则continue。
class Solution {
public:
    vector<vector<string>> result;
    vector<string> path;

    bool huiwen(string sub){
        int i = 0, j = sub.size() - 1;
        while(i<j){
            if(sub[i] != sub[j]) return false;
            i++;
            j--;
        }
        return true;
    }

    vector<vector<string>> partition(string s) {
        if(s.size()) backtracking(s,0);
        return result;
    }
    void backtracking(string s,int startIndex){
        
        if(startIndex == s.size()){
            result.push_back(path);
            return;
        }
        //从startIndex开始,把后序字符串依次尝试分割为长度为1,2,...s.size()-startIndex+1的字串
        for(int i = startIndex;i < s.size();i++){ //i用于记录字串的结尾位置
            string sub = s.substr(startIndex,i - startIndex + 1); //下标为startIndex-i的子串
            if(!huiwen(sub)) continue;
            path.push_back(sub);
            backtracking(s,i + 1);
            path.pop_back();
        }
        return;
    }
};

关于判断是否回文串:(回文 palindrome

    bool isPalindrome(string sub){
        int i = 0, j = sub.size() - 1;
        while(i<j){
            if(sub[i] != sub[j]) return false;
            i++;
            j--;
        }
        return true;
    }

二、学习文章后收获

1.复杂度

  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n^2)

2.对比“分割”问题和“组合”问题

  • 最大的不同,从每层回溯的子集上考虑:
    • 组合:子集中是单个元素, 比如{2,3,4},用for循环遍历这些元素
    • 分割:子集中是来源于同一字符串、长度为1~k的子串,它们起始于同一位置,但长度各不相同, 比如从”aabcd“可以得到{”a“,“aa”,“aab”,“aabc”,“aabcd”}。for循环控制子串长度,从而生成这些子串,遍历这些情况。

3.判断回文串的优化(动态规划,后续补上)

三、过程中遇到的问题

1.string常用的操作

# include<string>

string str;

//子串
str.substr(pos,npos) //取子串,开始位置pos,长度npos

//插入
str.insert(pos,c) //开始位置pos,插入一个字符c
str.insert(pos,s) //开始位置pos,插入一个字符串s
str.insert(pos,n,c) //开始位置pos,插入n个字符c

//删除
str.erase(pos,npos) //删除一部分子串,开始位置pos,长度npos

//查找
str.find(s) //查找s这个子串第一次出现的位置,找不到返回-1
str.find(s,pos = 0) //从pos位置开始(默认0),查找s这个子串第一次出现的位置
str.find(c,pos = 0) //从pos位置开始(默认0),查找c这个字符第一次出现的位置
str.find(s,pos,n) //从pos位置开始(默认0),查找s这个字符串串的前n个字符第一次出现的位置

//右查找
str.rfind(s)
str.rfind(s,pos = 0)
str.rfind(c,pos = 0)
str.rfind(s,pos,n)

//替换
str.replace(pos,n,str) //从pos开始的n个字符替换成str

//字符串比较
str.compare(s) //比较str和s。字符串比较是按字符的ASCII码进行对比,返回0则相等!


  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值