C++算法学习心得六.回溯算法(2)

1.组合总和(39题)

题目描述:

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

  • 输入:candidates = [2,3,6,7], target = 7,
  • 所求解集为: [ [7], [2,2,3] ]

回溯法: 如果是一个集合来求组合的话,就需要startIndex,如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,只是组合情况,与之前组合问题不一致的是该题可以重复选择,主要在于递归函数的startindex选取,

class Solution {
private: vector<int>path;//路径vector<vector<int>>result;//结果集//回溯函数,需要参数:数组,目标,和,组合问题需要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]);//路径加入backtracking(candidates,target,sum,i);//递归path.pop_back();//回溯sum -= candidates[i];//回溯}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {backtracking(candidates,target,0,0);return result;}
};

减枝操作:对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历

class Solution {
private: vector<int>path;//路径vector<vector<int>>result;//结果集//回溯函数,需要参数:数组,目标,和,组合问题需要startindex来解决void backtracking(vector<int>& candidates,int target,int sum,int startindex){if(sum > target){return ;//如果和大于目标值直接返回}if(sum == target){result.push_back(path);//和和目标值相等,将路径加入结果集中return ;}//组合数组的遍历,遍历的集合树的宽度,排序进行,如果sum+该值大于了目标值就不进行操作实现减枝for(int i = startindex;i < candidates.size() && sum + candidates[i] <= target;i++){sum += candidates[i];//和求法path.push_back(candidates[i]);//路径加入backtracking(candidates,target,sum,i);//递归path.pop_back();//回溯sum -= candidates[i];//回溯}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {sort(candidates.begin(),candidates.end());//排序为了减枝backtracking(candidates,target,0,0);return result;}
};
  • 时间复杂度: O(n * 2^n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
  • 空间复杂度: O(target)

无数量要求终止条件发生改变,元素重复选用递归函数体现采用i而不是i+1,组合排序之后在进行减枝经常的操作

2. 组合总和II(40题)

题目描述:

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合。

  • 示例 1:
  • 输入: candidates = [10,1,2,7,6,1,5], target = 8,
  • 所求解集为:
[[1, 7],[1, 2, 5],[2, 6],[1, 1, 6]
]

回溯法:集合(数组candidates)有重复元素,但还不能有重复的组合,组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过,树层去重的话,需要对数组排序,加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
used[i - 1] == false,说明同一树层candidates[i - 1]使用过

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,sum + candidates[i] <= target为剪枝操作

class Solution {
private: vector<int>path;//路径vector<vector<int>>result;//结果集//回溯函数,需要参数:数组,目标,和,组合问题需要startindex来解决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 ;}//组合数组的遍历,遍历的集合树的宽度,排序进行,如果sum+该值大于了目标值就不进行操作实现减枝for(int i = startindex;i < candidates.size() && sum + candidates[i] <= target;i++){if(i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false){continue;//我们使用used数组来进行处理去重的逻辑,先排序,如果前一个值和后一个值相等,且用过数组i-1位置没用过,需要去重,也就是需要在树层来进行操作,}sum += candidates[i];//和求法path.push_back(candidates[i]);//路径加入used[i] = true;//使用过变为truebacktracking(candidates,target,sum,i+1,used);//递归path.pop_back();//回溯sum -= candidates[i];//回溯used[i] = false;//之后回溯}}
public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {vector<bool>uesd(candidates.size(),false);//定义使用过的数组sort(candidates.begin(),candidates.end());//排序为了减枝backtracking(candidates,target,0,0,uesd);return result;}
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

3.分割回文串(131题)

题目描述:

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例: 输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]

回溯法:切割问题类似组合问题,

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。

递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的 

切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件,[startIndex, i] 就是要截取的子串,切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1

class Solution {
private:vector<vector<string>>result;//结果集,注意里面的存放形式vector<string>path;//路径也是如此//回溯函数,注意参数,这里需要startindex作为集和选取组合类似操作,都是在一个集和选取需要startindexvoid backtracking(const string& s,int startindex){//如果起始位置已经大于s的大小,说明已经找到了一组分割方案了if(startindex >= s.size()){result.push_back(path);//终止条件,我们需要把找到的回文子串加入结果当中return;}//这个仍需要遍历树的宽度,也就是集和下标开始位置for(int i = startindex;i < s.size();i++){//判断是否是回文子串,注意下标从,startindex开始到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;}
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n^2)

4.复原IP地址(93题)

题目描述:

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。

示例 1:

  • 输入:s = "25525511135"
  • 输出:["255.255.11.135","255.255.111.35"]

回溯法: 切割问题就可以使用回溯搜索法把所有可能性搜出来,切割问题可以抽象为树型结构,

startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。还需要一个变量pointNum,记录添加逗点的数量。递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.)回溯的时候,就将刚刚加入的分隔符. 删掉就可以了,pointNum也要-1。

class Solution {
private:vector<string>result;//结果集,需要接收字符串//我们定义一个回溯函数,参数startindex,句号void backtracking(string& s,int startindex,int pointnum){//如果句号三个,三个句号四段,所以可以实现收割结果if(pointnum == 3){//判断是否合法,第四段,如果合法就加入结果中if(isvalid(s,startindex,s.size()-1)){result.push_back(s);//将修改的字符串加入结果中}return;}//组合和切割问题类似的startindex,for(int i = startindex;i < s.size();i++){//如果合法,我们开始对其操作if(isvalid(s,startindex,i)){s.insert(s.begin()+i+1,'.');//记得在开始+i位置+1加入.pointnum++;//句号要增加操作backtracking(s,i+2,pointnum);//注意这里需要i+2,是因为有个.存在s.erase(s.begin()+i+1);//回溯pointnum--;//回溯}else break;//不合法,退出}}//判断是否合法bool isvalid(const string& s,int start,int end){if(start > end){return false;//}if(s[start] == '0' && start != end){return false;//0开头的数字不合法}int num = 0;for(int i = start;i <= end;i++){if(s[i] < '0' || s[i] > '9'){return false;//遇到非数字字符不合法}num = num*10 + (s[i] - '0');//获取当前数字if(num > 255){return false;//如果大于255了不合法}}return true;}
public:vector<string> restoreIpAddresses(string s) {result.clear();if (s.size() < 4 || s.size() > 12) return result;//减枝操作backtracking(s,0,0);//回溯return result;}
};
  • 时间复杂度: O(3^4),IP地址最多包含4个数字,每个数字最多有3种可能的分割方式,则搜索树的最大深度为4,每个节点最多有3个子节点。
  • 空间复杂度: O(n)

 5.子集(78题)

题目描述:

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例: 输入: nums = [1,2,3] 输出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]

回溯法:组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点,子集也是一种组合问题,因为它的集合是无序的,既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始,求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。

class Solution {
private:vector<int>path;vector<vector<int>>result;void backtracking(vector<int>& nums,int startindex){result.push_back(path);//别忘了这个if(startindex > nums.size()){return;//终止条件}for(int i = startindex;i < nums.size();i++){path.push_back(nums[i]);//加入路径backtracking(nums,i+1);//递归path.pop_back();//回溯}}
public:vector<vector<int>> subsets(vector<int>& nums) {backtracking(nums,0);return result;}
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

总结: 

组合总和:这里是一个集合但是元素可以重复选取,一个集合startindex,两个集合就是index,只不过不一样的是递归函数传参是i,就可以实现,注意减枝操作可以在树的宽度遍历变量做文章,对i进行限制,但是需要对数组进行排序

组合总和II:集合中有重复元素,但是结果中不可以出现重复,所以设计了去重的操作,这里我们使用一个数组去记录树层的是否使用了,布尔类型数据,我们依然需要排序,然后再对数组进行判断,如果相邻两个元素相等,且是树层情况下,那么不做操作,回溯也需要对判断数组进行回溯

分割回文串:切割问题类似组合问题,切割线就是startindex,切割字符串就是【startindex,i】,我们需要截取子串,回溯和组合类型相似,从i+1开始,注意回文子串判断函数的写法,

复原IP地址:这里我们做的复原Ip,需要考虑,终止条件,我们定义句号,加入三个句号就处理结束,然后需要判断是否切割的合法,合法加入结果,如果切割合法我们进行递归和回溯操作,还要写一个判断是否合法的函数,考虑各种非法情况来写相应的处理,

子集: 子集和组合问题最大区别在于,组合问题是查找叶子节点的,但是子集问题需要将所有结果返回,也需要startindex,来操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/629367.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 循环结构之for循环

在Python中&#xff0c;循环结构用于重复执行一段代码&#xff0c;是非常重要的编程方法&#xff0c;其中for循环是特别常用的循环结构。 一、理解&#xff1a; for循环用于遍历一个可迭代对象&#xff08;如列表、元组、字符串等&#xff09;中的元素&#xff0c;或者执行固…

深入理解单例模式:如何确保一个类只有一个实例?

欢迎来到英杰社区 https://bbs.csdn.net/topics/617804998 欢迎来到阿Q社区 https://bbs.csdn.net/topics/617897397 单例模式 前言单例模式饿汉模式懒汉模式 前言 单例模式&#xff08;Singleton Pattern&#xff09;是一种常用的设计模式&#xff0c;用于确保一个类只有一个…

【大模型评测】常见的大模型评测数据集

开源大模型评测排行榜 https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard 其数据是由其后端lm-evaluation-harness平台提供。 数据集 1.英文测试 MMLU https://paperswithcode.com/dataset/mmlu MMLU&#xff08;大规模多任务语言理解&#xff09…

公网对讲|酒店无线对讲系统

提高工作效率 酒店对讲机可以帮助酒店员工实现快速、有效的内部沟通&#xff0c;从而提高服务质量。例如&#xff0c;前台接待人员可以通过对讲机及时通知客房服务人员为客人提供快速入住服务&#xff0c;或者通知餐饮部门为客人提供送餐服务。此外&#xff0c;对讲机还可以帮…

我劝你千万不要去做CSGO游戏搬砖

大家好&#xff0c;我是阿阳。今天我要给大家讲解一下做CSGO游戏搬砖项目前必须知道的五个问题。作为一个做这个项目已经三年多的老手&#xff0c;我带过的搬砖学员已经有好几百人了。在这个过程中&#xff0c;也积累了不少经验和教训&#xff0c;希望能够通过这篇文章给大家一…

Oracle数据库避坑:CASE WHEN ‘ ‘ = ‘ ‘ 空字符串比较,预期的结果与判断逻辑的实现之间存在不匹配

Oracle数据库避坑&#xff1a;CASE WHEN 空字符串比较&#xff0c;预期的结果与判断逻辑的实现之间存在不匹配 1、背景2、具体示例分析3、其他相同案例4、结论 1、背景 在业务开发中&#xff0c;查询sql视图时&#xff0c;使用CASE WHEN语句判断空字符串是否不等于column…

Vue创建项目配置情况

刚开始接触vue项目创建和运行因为node版本和插件版本不一致时长遇到刚装好插件&#xff0c;项目就跑不起来的情况&#xff0c;特此记录一下 vue -V vue/cli 5.0.8 node -v v12.22.12 npm -v 6.14.16 关闭驼峰命名检查、未使用语法检查 package.json文件内容&#xff1a; {&…

数学建模常见算法的通俗理解(更新中)

目录 1.层次分析法&#xff08;结合某些属性及个人倾向&#xff0c;做出某种决定&#xff09; 1.1 粗浅理解 1.2 算法过程 1.2.1 构造判断矩阵 1.2.2 计算权重向量 1.2.3 计算最大特征根 1.2.4 计算C.I.值 1.2.5 求解C.R.值 1.2.6 判断一致性 1.2.7 计算总得分 2 神经网…

Verdaccio中,创建私服时,如何用VERDACCIO_PUBLIC_URL修改页面上资源文件的域名

更多内容&#xff0c;欢迎访问&#xff1a;Verdaccio npm私服时&#xff0c;遇到更多问题 用 Verdaccio 搭建私服时&#xff0c;当使用定义的域名访问时&#xff0c;报错&#xff0c;原因是JS等资源文件的访问域名是 127.0.0.1:4873&#xff0c;并不是我们想要的域名: 通过查看…

2024年第二届“华数杯”国际大学生数学建模竞赛 (B题 ICM)| 光伏发电分析 |数学建模完整代码+建模过程全解全析

光伏发电是一种重要的可再生能源。将太阳能转化为电力可以减少对传统能源的依赖,具有显著的环保和可持续发展优势。全球范围内,光伏发电正在迅速发展。目前,许多国家将光伏发电作为推动清洁能源转型的重要手段。这些国家在政策支持、技术创新和市场发展方面增加了对光伏发电的投…

视频改字视频制作系统,祝福视频,告白视频改字系统搭建开发定制

一、视频改字制作系统功能介绍&#xff1a; 素材同步&#xff0c;极速下载&#xff0c;会员充值&#xff0c;达人分销&#xff0c;积分系统&#xff0c;精美UI&#xff0c; 卡密兑换&#xff0c; 直播挂载&#xff0c; 五端兼容&#xff1a;微信小程序&#xff0c;抖音小程序&…

Kafka-RecordAccumulator分析

前面介绍过&#xff0c;KafkaProducer可以有同步和异步两种方式发送消息&#xff0c;其实两者的底层实现相同&#xff0c;都是通过异步方式实现的。 主线程调用KafkaProducer.send方法发送消息的时候&#xff0c;先将消息放到RecordAccumulator中暂存&#xff0c;然后主线程就…

JVM实战(23)——内存碎片优化

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

I2C总线和通信协议详解 (超详细配42张高清图+万字长文)

I2C总线和通信协议详解 (超详细配42张高清图万字长文) I2C&#xff08;Inter-Integrated Circuit&#xff09;通信总线&#xff0c;作为嵌入式系统设计中的一个关键组成部分&#xff0c;其灵活性和高效率使其在高级应用中备受青睐。本文旨在提供关于I2C通信总线的深度解析&…

认识并使用JWT

认识并使用JWT 一、互联网世界的用户认证二、对JWT的基本认知三、JWT的原理1 Header2 Payload3 Signature4 [参考资料](https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html) 四、使用JWT1、引入依赖2、jwt的生成与解析3、测试3.1 生成jwt3.2 解析jwt 一、互…

DataXCloud部署与配置[智数通]

静态IP设置 # 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33# 修改文件内容 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.18.130 NETMASK255.255.255.0 GATEWAY192.168.18.2 DEFROUTEyes IPV4_FAILURE_FATALno IPV6INIT…

Pytorch各种Dropout层应用于详解

目录 torch框架Dropout functions详解 dropout 用途 用法 使用技巧 参数 数学理论公式 代码示例 alpha_dropout 用途 用法 使用技巧 参数 数学理论公式 代码示例 feature_alpha_dropout 用途 用法 使用技巧 参数 数学理论 代码示例 dropout1d 用途 用…

Spring 整合Shiro鉴权授权

一、什么是Shiro shiro是apache的一个开源框架&#xff0c;是一个权限管理的框架&#xff0c;实现 用户认证、用户授权。spring中有spring security &#xff0c;是一个权限框架&#xff0c;它和spring依赖过于紧密&#xff0c;没有shiro使用简单。 shiro不依赖于spring&#…

SQL实践:利用tag检索文件的多种情况讨论(二)

在上一篇文章SQL实践&#xff1a;利用tag检索文件的多种情况讨论中&#xff0c;我们介绍了在使用外键的方式为数据关联tag后&#xff0c;如何筛选&#xff1a; 如何筛选包含某一个tag的数据如何筛选包含且只包含某一个tag的数据如何筛选包含多个指定tag的数据 这篇文章主要是…

Linux 网口配置文件及网络服务

本篇记录Linux 的网卡配置以及网络服务的相关配置&#xff0c;期望在了解的网卡的配置内容的基础上&#xff0c;对网络问题能进行配置文件的排查。网络问题是非常复杂的&#xff0c;本篇不涉及抓包和网络的其他问题排查。 一、网络配置文件 网络配置文件有很多&#xff0c;常见…