当前位置: 首页 > news >正文

代码随想录算法训练营第60期第二十七天打卡

        五一假期结束了,今天开始恢复正常打卡,今天我们将会接着上一次的回溯问题继续进行,今天我们主要是组合问题,我们在回溯算法的理论基础里面讲到过回溯算法可以解决的一类问题就是组合问题,那我们今天就重点来看看我们是如何运用回溯算法来解决组合问题的。

第一题对应力扣编号为39的题目组合总和

       这一道题目是组合总和,这个是不是与我们上一次的组合不一样了,其实我感觉应该是要求更多了,因为我们找到的组合还得要求满足和为题目给出的值,但是似乎我们没有数量限制了,我们来看一下具体的题目要求:

        看到题目我们先看一下有一个很重要的事情就是同一个元素是否允许使用多次,很明显这道题目是允许使用多次的,那我们就可以尝试使用回溯算法写出代码,还是遵循递归三部曲,但是大家注意这里我们还需要一个变量就是需要存储当前我取的元素的和,这个很重要这关系到我们的递归终止条件,因此我们递归与回溯相结合就可以写出代码:

class Solution {
private:vector<int> path;vector<vector<int>> result;//明确没一个变量的含义sum表示当前的和startIndex表示我可以递归的起点索引void backtracking(vector<int> &candidates, int target, int sum, int startIndex){if (sum > target) return;if (sum == target){result.push_back(path);return;}for (int i = startIndex; i < candidates.size(); ++i){sum += candidates[i];path.push_back(candidates[i]);//由于允许有重复所以还可以继续从i继续递归backtracking(candidates, target, sum, i);sum -= candidates[i];//回溯path.pop_back();}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();backtracking(candidates, target, 0, 0);return result;}
};

        大家注意递归调用的参数,也就是那个startIndex参数我们要从i递归因为题目说了允许一个元素使用多次,后面的就不允许使用多次了,我们递归参数就需要改变了,我们每往一维数组里面添加一个元素我们就需要记录下此时的和,当我们回溯的时候我们记得要从和里减去当前元素就可以了,总体只要掌握了递归三部曲这道题目还是不难的。还有大家要注意我们递归本质是对一棵树进行操作,因此我们发现当前的和大于目标值的时候就直接return这种情况显然不是我们想要的。

第二题对应力扣编号为40的组合总和II

        我们来到了今天的第二题,这一道题就有难度了,其实难度在于去重,因为题目要求了不允许出现相同的组合,而且每一个元素只能使用一次,这个就有难度了,尤其是去重,我们先来看一下题目的要求:

       看到示例一有朋友可能就会有疑问,为啥会出现两个1,不是每一个元素只能使用一次吗?大家注意我们的原数组里存在两个值一样的元素,所以我们这里的两个1是两个元素,因此这是符合题目要求的,理解这里对我们理解后面的去重也很重要,我们后面的去重就要避免去掉这种情况,为了让大家理解,代码随想录上引入了树层与树枝两个概念,重点是对树层去重,树枝重点是避免同一个元素使用多次,我们看一下给出的示意图:

       注意理解示意图的逻辑,我们是一个树形结构,其实我们原先的for循环的过程就是构造树层的过程,我们的递归与回溯的过程就是构造树枝的过程,我们在树枝是允许出现值相等的元素的,因为我们最初集合里本就可能出现两个值一样的元素,但是大家注意我们树层就不允许一样了,因为一旦树层一样就会导致重复,我这次取出1去递归回溯,我可以找出【1,2】这个组合,如果我树层里还要取出1的话那么我们回溯递归还是会找到【1,2】这样的组合因此会出现重复,这个要注意去重,但是我们又要避免去掉示例1的情况,因此我们要对数组排序,并且第一个1是要使用的,否则就会去掉不应该去的情况,

       这个图比较难理解,注意蓝色字体的描述,我们是允许树枝上出现相同元素的,但树层不允许,我们可以写出如下代码:

class Solution {
private:vector<int> path;vector<vector<int>> result;void backtracking(vector<int> &candidates, int target, int sum, int startIndex, vector<bool> &used){//递归的终止条件if (sum > target) return;if (sum == target){result.push_back(path);return;}//单层递归的逻辑for (int i = startIndex; i < candidates.size(); ++i){//树层去重(注意题目要求不能出现重复的组合而且每一个元素只能出现一次如果两个元素相等的话其实是相同数值的元素可以出现多次)if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) continue;sum += candidates[i];path.push_back(candidates[i]);used[i] = true;backtracking(candidates, target, sum, i + 1, used);sum -= candidates[i];path.pop_back();used[i] = false;}}
public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {vector<bool> used(candidates.size(), false);result.clear();path.clear();sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0, used);return result;}
};

      重点理解这个去重操作,注意树层去重就可以了,这道题我就给大家解释到这里,我们接着下一道题目。

第三题对应力扣编号为131的题目分割回文串

       我们来到了最后一题,其实这道题大家仔细阅读思考就会发现这也是一道组合问题,

       这个组合找的是回文串的组合,因此我们应该先写出判断回文串的函数,然后再去递归回溯,但这里我们需要注意的是递归的终止条件应该如何写,还有一个就是判断回文串的函数,大家读完题目就可以发现这里很明显字符串的每一个元素都只能使用一次,我直接给出代码解释一下:

class Solution {
private:vector<string> path;vector<vector<string>> result;void backtracking(const string &s, int startIndex){//注意递归的终止条件是我当前的索引大于等于字符串的长度if (startIndex >= s.size()){result.push_back(path);return;}//单层递归的逻辑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();//回溯将添加进去的字符串弹出来这样才可以确保我们可以找到所有的字符串}}//判断回文串的函数就是使用类似于双指针的思想逐一对比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;}
};

      这是这道题目的完整代码,包括了判断回文字符串以及递归回溯的过程,模拟切割线,其实就是startIndex是上一层已经确定了的分割线,i是这一层试图寻找的新分割线这点要注意,这也是题目的难点,希望大家可以好好理解一下。

总结

       回溯算法其实是一种很有难度的算法,大家一定要多理解,其实通过这几道题目大家也可以发现我们存在一套模板,大家务必掌握好递归三部曲,意识到递归与回溯不分家就可以了,今天就先到这里,我们明天见!

相关文章:

  • ABC 404
  • sudo useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama解释这行代码的含义
  • 机器人强化学习入门学习笔记(二)
  • HTML05:超链接标签及应用
  • 永磁同步电机控制算法--基于PI和前馈的位置伺服控制
  • 告别(Python)if elif else错误使用方法
  • 介绍分治、动态规划、回溯分别是什么?有什么联系和区别?给出对象的场景和java代码?
  • 【硬核攻坚】告别CUDA OOM!DeepSeek部署显存瓶颈终极解决方案:三大策略高效落地
  • day04_计算机常识丶基本数据类型转换
  • 15.日志分析入门
  • 架构思维:构建高并发读服务_热点数据查询的架构设计与性能调优
  • 三维重建(二十一)——第二步和第三步
  • 数据集-目标检测系列- 印度人脸 检测数据集 indian face >> DataBall
  • 对于1年来开发的程序化股票交易的做一个总结
  • linux inotify 资源详解
  • 【Qt】配置环境变量
  • 《赤色世界》彩蛋
  • 如何判断node节点是否启用cgroup?
  • Windows 自带删除缓存
  • VTK 数据读取/写入类介绍
  • 独家专访|白先勇:我的家乡不是哪个地点,是中国传统文化
  • 经济日报头版刊文:为什么贸易战没有出路
  • 许昌市场监管部门对胖东来玉石开展日常检查:平均毛利率不超20%
  • 日本儿童人数已连续44年减少,少子化问题越发严重
  • 国际油价重挫!美股道指连跌三月,啥情况?
  • 解放日报:人形机器人新赛道正积蓄澎湃动能