Studying-代码随想录训练营day22| 回溯理论基础、77.组合、216.组合总和II、17.电话号码的字母组合

第22天,回溯章节开始!一大算法难点,加油加油!

回溯理论基础组合问题的剪枝操作

文档讲解:代码随想录回溯理论基础

视频讲解:回溯理论基础

回溯法也叫回溯搜索法,它是一种搜索,遍历的方式。回溯是递归的副产品,只要有递归就会有回溯,回溯本质就是递归过程中,到达底端(终止条件)后的不断返回过程。

回溯法的效率:回溯法的本质是穷举,它的效率并不高,是一种暴力解法,但有些时候我们就是需要这种暴力的解法,再适当增加剪枝解决问题。

回溯法解决的问题:回溯法,一般可以解决如下几种问题。

  • 组合问题:N个数里面按一定规则找出k个数的集合。
  • 切割问题:一个字符串按一定规则有几种切割方式。
  • 子集问题:一个N个数的集合里有多少符合条件的子集。
  • 排列问题:N个数按一定规则全排列,有几种排列方式。
  • 棋盘问题:N皇后,解数独等等。

这些问题都不好解决,N值较小的情况还能够通过for循环进行遍历,但一旦N值增大,for循环就难以进行编写了,而且在我们无法确定要循环多少层的时候,也无法使用for循环。这个时候就需要使用回溯的方法递归的进行了。

回溯法的结构:回溯法解决的问题都可以抽象为n叉树的树形结构。例如在求集合的问题中,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。因此在运用回溯法解题的时候,我们都可以通过画一个树状图来更好的理解递归的逻辑。

回溯法的模板:回溯法在进行编写的时候,主要需要注意三部,与递归三部曲相同的回溯三部曲。

  • 回溯函数模板返回值以及参数:回溯算法中返回值一般为void,参数列表需要根据你的递归逻辑来一个个确定参数。
void backtracking(参数)
  • 回溯函数终止条件:即满足一定的条件,把答案存储下来,并结束本层递归。回溯函数中肯定需要有终止条件,不能陷入无止尽的递归,那样会导致栈溢出。因此回溯构成的树形结构一定是一个高度有限的树。
if (终止条件) {存放结果;return;
}
  • 回溯搜索的遍历过程:回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果
}

for循环就是遍历集合区间,可以理解为一个节点有多少孩子,这个for循环就执行多少次。

backtracking这里自己调用自己,实现递归。从图中可以看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

总结模板如下:

void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}

77.组合

文档讲解:代码随想录组合

视频讲解:手撕组合问题、

题目:

学习:本题是回溯算法的第一道题,也是利用回溯算法经典的题目之一。显然对于第一个示例n = 4,k = 2的情况来说,我们可以通过两个for循环很容易的就能得到所有的组合结果,但是当n,k不断增大,至少需要k个for循环才能够遍历所有的节点,直接进行顺序代码编写的时候根本无法进行,因此本题需要采用回溯算法,事实上就是通过递归的方式来进行for循环,本质就是一层递归就是一层for循环。用树形结构来解释本题的回溯过程,如下图:

代码: 

//时间复杂度O(n*2^n)
//空间复杂度O(n)
class Solution {
public://设置全局变量,减少参数数量,避免引用vector<vector<int>> result;vector<int> path;//确定回溯参数,由于是组合问题,组合中元素不存在顺序,因此还需要一个下标来指示当前遍历的位置void backtracking (int n, int k, int idnex) {//确定回溯终止条件if(path.size() == k) {result.push_back(path);return;}//单层递归逻辑for (int i = idnex; i <= n; i++) {path.push_back(i);backtracking(n, k, i + 1);//回溯处理path.pop_back();}}vector<vector<int>> combine(int n, int k) {backtracking(n, k, 1);return result;}
};

本题还可以进行剪枝处理,显然上题中取4这一步是不需要的,因为已经构不成两个数了。当n=4,k=4中不需要的步骤会显得更多:

图中每一个节点就代表本层的for循环,剪枝的关键在于如何确定本层for循环的终点位置。

显然当for循环选择的位置之后的元素个数,不足我们需要的元素个数的时候,就没有必要搜索了。本题中已经选择的元素个数为path.size(),所需需要的元素个数为k - path.size(),列表中剩余元素个数(n - i)需要大于等于所需要的元素个数k - path.size()。因此i最多遍历到n - (k - path.size()) + 1的位置,也即 i <= n - (k - path.size()) + 1,为什么有个+1因为我们要把当前元素加上,这个通过例子模拟一下就能够理解。

因此优化后的for循环应该是:

for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置

优化后的代码为:

class Solution {
public://设置全局变量,减少参数数量,避免引用vector<vector<int>> result;vector<int> path;//确定回溯参数,由于是组合问题,组合中元素不存在顺序,因此还需要一个下标来指示当前遍历的位置void backtracking (int n, int k, int idnex) {//确定回溯终止条件if(path.size() == k) {result.push_back(path);return;}//单层递归逻辑for (int i = idnex; i <= n - (k - path.size()) + 1; i++) {path.push_back(i);backtracking(n, k, i + 1);//回溯处理path.pop_back();}}vector<vector<int>> combine(int n, int k) {backtracking(n, k, 1);return result;}
};

总结:画图会更有助于我们进行算法的剪枝处理。 


216.组合总和II 

文档讲解:代码随想录组合总和III

视频讲解:手撕组合总和III

题目: 

学习:本题与上题的不同在于遍历的集合确定了是1到9,n和k共同构成了遍历过程中需要判断的条件。本题中k相当于树的深度,9就是树的宽度,假如k=2,n=4用树形结构来表示就是:

代码: 回溯三部曲

//时间复杂度O(n*2^n)
//空间复杂度O(n)class Solution {
public:vector<vector<int>> result; // 存放结果集vector<int> path; // 符合条件的结果void backtracking(int n, int k, int val, int index) { //val已经收集的元素的总和,也就是path里元素的总和//终止条件if(path.size() == k) {if(val == n) {result.push_back(path);}return; // 如果path.size() == k 但sum != targetSum 直接返回}//单层递归逻辑for(int i = index; i <= 9 - (k - path.size()) + 1; i++) {val += i;path.push_back(i);backtracking(n, k, val, i + 1); //注意调整index遍历的集合范围path.pop_back();  //回溯val -= i;  //回溯}}vector<vector<int>> combinationSum3(int k, int n) {backtracking(n, k, 0, 1);return result;}
};

本题同样可以进行剪枝处理,例如上图中,后面有很大部分就不需要进行。

本题可以进行两部分的剪枝。1.首先适合上一道题相同的,依据k进行的集合大小的剪枝,也就是i只能遍历到9 - (k - path.size()) + 1;2.我们可以根据n进行剪枝,当加和val大于n的时候,就可以返回了,不需要继续进行集合后面数字的遍历,因为后面的数字加进来也一定会大于n,已经超出了我们所需要的值。

依据上述两点,代码进行剪枝处理后:

class Solution {
public:vector<vector<int>> result;vector<int> path;void backtracking(int n, int k, int val, int index) {if(path.size() == k) {if(val == n) {result.push_back(path);}return;}//两个剪枝操作//9 - (k - path.size()) + 1 对范围剪枝,不够k个元素就不用遍历for(int i = index; i <= 9 - (k - path.size()) + 1; i++) {val += i;//值剪枝,大于n就不用遍历了if(val > n) {return;}path.push_back(i);backtracking(n, k, val, i + 1);path.pop_back();val -= i;}}vector<vector<int>> combinationSum3(int k, int n) {backtracking(n, k, 0, 1);return result;}
};

17.电话号码的字母组合

文档讲解:代码随想录电话号码的字母组合

视频讲解:手撕电话号码的字母组合

题目:

学习:显然本题也是一个不断循环匹配的过程。依据题干可以把本题分解为3步:1.拆分digits中的数字,并使数字与对应的字母串映射匹配;2.根据顺序依次进行循环遍历。3.保存结果集。例如依据”23“示例,我们可以给出回溯对应的树形图:

对于第1个问题,我们可以通过设置一个哈希表或者map来完成每个数字对字母串的映射,因为本题中数字数量有限且是顺序排列的,因此可以用一个数组来完成映射。

对于2,3问题,就是使用回溯算法来进行模拟遍历了。 

代码: 确定回溯三部曲。

//时间复杂度: O(3^m * 4^n),其中 m 是对应三个字母的数字个数,n 是对应四个字母的数字个数
//空间复杂度: O(3^m * 4^n)
class Solution {
private://使用一个数组来充当哈希表,对应每个字母string letter[10] = {" ", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public:vector<string> result;string s;//确定返回值和参数,参数中需要一个index来指示遍历到第几个数字了void backtracking(string& digits, int index) {//确定终止条件,当index--digits.size()时说明所有的数字都遍历完了,因为最后一个数字的下标为digits.size()-1if(index == digits.size()) {result.push_back(s);return;}//取出数字对应的字符串int dig = digits[index] - '0';string list = letter[dig];//确定单层递归逻辑for(int i = 0; i < list.size(); i++) {s.push_back(list[i]);backtracking(digits, index + 1);s.pop_back();}return; //可写可不写}vector<string> letterCombinations(string digits) {if (digits.size() == 0) {return result;}backtracking(digits, 0);return result;}
};

总结:

回溯算法是一个不好理解的算法,初期过程中可以通过画树形图的方式来加深理解!

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

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

相关文章

Arathi Basin (AB) PVP15

Arathi Basin &#xff08;AB&#xff09; PVP15 阿拉希盆地&#xff0c;PVP&#xff0c;15人战场

放大招了|十亿参数大模型LLMs运行功耗仅需13W,内存使用量减少90%!

矩阵乘法&#xff08;MatMul&#xff09;历来是大型语言模型&#xff08;LLMs&#xff09;总体计算成本的主导因素&#xff0c;尤其在模型向更大维度嵌入和上下文长度发展时&#xff0c;这一成本呈指数级增长。 近期有一篇刚刚发表的论文中提出的方法完全去除了矩阵乘法操作&am…

Oracle ACE夜话--和首席-总监一起跟大家谈一谈分布式数据库发展

前言 在昨日的Oracle ACE夜话中&#xff0c;和薛首席、尹总监一起聊了下当前分布式数据库的内容&#xff0c;现将分享内容又进行了相应整理&#xff0c;分享给大家&#xff0c;也希望大家多多指正。 一、自我介绍 尚雷&#xff0c;公众号【尚雷的驿站】&#xff0c;PG ACE&a…

C++项目实践学习笔记---DLL

linux守护进程 守护进程或精灵进程&#xff08;Daemon&#xff09;&#xff1a;以后台服务方式运行的进程&#xff0c;它们不占用终端&#xff08;Shell&#xff09;&#xff0c;因此不会受终端输入或其他信号&#xff08;如中断信号&#xff09;的干扰守护进程有如下特点。 &…

高性能并行计算课程论文:并行网络爬虫的设计与实现

目录 1.绪论 1.1 研究背景 1.2 研究意义 ​​​​​​​1.3 文章结构 2. 网络爬虫相关理论 ​​​​​​​2.1 URL地址格式 ​​​​​​​2.2 网页爬取策略 2.2.1 深度优先策略 2.2.2 广度优先策略 2.2.3 最佳优先策略 ​​​​​​​2.3 网页分析算法 ​​​​​​​2.3.1 正…

微软发布Phi-3系列语言模型:手机端的强大AI助手

大模型&#xff08;LLMs&#xff09;在处理复杂任务时展现出的巨大潜力&#xff0c;但却需要庞大的计算资源和存储空间&#xff0c;限制了它们在移动设备等资源受限环境中的应用。微软公司最新发布的Phi-3系列语言模型&#xff0c;以其卓越的性能和小巧的体积&#xff0c;打破了…

c++习题01-ljc的暑期兼职

目录 一&#xff0c;题目描述 二&#xff0c;思路 三&#xff0c;伪代码 四&#xff0c;流程图 五&#xff0c;代码 一&#xff0c;题目描述 二&#xff0c;思路 1&#xff0c;根据题目要求需要声明4个变量&#xff1a;a,b,c,d ;牛奶价格a&#xff0c;活动要求b&…

柯桥商务英语培训under是“在下面”,dog是“狗”,那underdog是什么意思?

英语中有很多单词 拆开看都认识 但合在一起意思就变了 就比如这个表达&#xff1a;underdog 大家都知道&#xff1a;under是下面&#xff0c;dog是狗 那么underdog是啥意思呢&#xff1f; 在小狗下面&#xff1f; 还是活得连狗都不如&#xff1f; 当然没有那么简单 但我…

flask 接收vuejs element el-upload传来的多个文件

el-upload通过action指定后端接口,并通过name指定传输的文件包裹在什么变量名中 <el-uploadclass="upload-demo"dragaction="https://ai.zscampus.com/toy/upload"multiplename="fileList":limit="10"accept=

Linux-笔记 使用SCP命令传输文件报错 :IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!

前言 使用scp命令向开发板传输文件发生报错&#xff0c;报错见下图; 解决 rm -rf /home/<用户名>/.ssh/known_hosts 此方法同样适用于使用ssh命令连接开发板报错的情况。 参考 https://blog.csdn.net/westsource/article/details/6636096

【Java Web】Axios实现前后端数据异步交互

目录 一、Promise概述 二、Promise基本用法 三、async和await关键字 四、Axios介绍 4.1 Axios基本用法 4.2 Axios简化用法之get和post方法 五、Axios拦截器 六、跨域问题处理 一、Promise概述 axios是代替原生的ajax实现前后端数据交互的一套新解决方案&#xff0c;而axios使用…

Geotools系列说明之入门介绍与坐标系转换说明

Geotools介绍 借用官网的介绍 GeoTools is an open source Java library that provides tools for geospatial data 就是提供java处理地理空间的工具详细内容请参考官网 Geotools环境搭建 我们这里只讨论Geotools在Maven中和Idea的项目实战&#xff0c;至于Geotools的服务端…

Filter and Search

应用筛选器时&#xff0c;“视图”仅显示符合当前筛选条件的记录。您可以根据一列或多列筛选数据。如果针对多个列进行筛选&#xff0c;则网格会在列筛选器之间应用AND逻辑运算符。 GridControl-Grid View 大多数DevExpress数据感知组件&#xff08;数据网格、树列表、垂直网…

讯飞星火企业智能体平台正式发布,打造每个岗位专属AI助手

大力财经 | 发布 讯飞星火V4.0来了&#xff01;6月27日&#xff0c;科大讯飞在北京发布讯飞星火大模型V4.0及相关落地应用。讯飞星火V4.0七大核心能力全面提升&#xff0c;整体超越GPT-4 Turbo&#xff0c;在8个国际主流测试集中排名第一&#xff0c;国内大模型全面领先。 大模…

台灯的功能作用有哪些?分享好用的护眼灯!看完就知道台灯怎么选

在当今时代&#xff0c;学生们长时间地沉浸于平板、手机、电脑等电子设备中&#xff0c;这些设备的屏幕往往伴随着频闪和蓝光辐射&#xff0c;这无疑对视力健康构成了潜在威胁。家长们日益关注孩子的护眼养眼问题&#xff0c;因为视力疲劳和眼部疾病不仅会降低个体的生活质量&a…

已解决:macOS Navicat保存密码失败(Failed to save password Error code: -34018),错误代码34018

Navicat 16 包括 Navicat15诸多版本都存在着问题&#xff0c;还要我去搞什么钥匙串访问&#xff0c;真麻烦&#xff01; Failed to save password Error code: -34018别搞那些有的没的方案&#xff01;就是TNT没 PJ 完美才导致这个问题出现&#xff0c;现在换了个PJ好的16.3.7版…

哈希表 | 哈希查找 | 哈希函数 | 数据结构 | 大话数据结构 | Java

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;毛毛张今天分享的内容&#x1f586;是数据结构中的哈希表&#xff0c;毛毛张主要是依据《大话数据结构&#x1f4d6;》的内容来进行整理&#xff0c;不…

上午写的博客,下午就上了bing首页,惊不惊喜,意不意外

今天上午写了一篇《用免费的“山水博客”来管理你的离线文章》的博客&#xff0c;没想到下午在必应就搜到了&#xff0c;而且还是首页第四个。 不由的让人感概&#xff0c;以前写了那么多的博客&#xff0c;想将排名排前点&#xff0c;在网上找了不少秘籍&#xff0c;都没成功&…

神经网络学习笔记9-简单的反向传播和线性回归

tensor在pytorch中是一个非常重要的类型 假如需要计算梯度&#xff0c;就将tensor中 requires_grad设为true loss是一个张量&#xff0c;在做运算时构建运算图&#xff0c;因此不要直接进行&#xff0c;会将将梯度存入w&#xff0c;当反向传播完成 后&#xff0c;该计算图会…

计算机的错误计算(十四)

摘要 解读 GPU和CPU计算上的精度差异&#xff1a;GPU 的 3个输出的相对误差分别高达 62.5%、50%、62.5%。 例1. 计算下列两个矩阵的乘积&#xff1a; 显然&#xff0c;其结果为第一列&#xff1a; 这个结果是准确的。 例2. 已知上面 3 个矩阵是由下面代码产生或输出&…