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…

grpc学习golang版( 三、proto文件数据类型)

系列文章目录 第一章 grpc基本概念与安装 第二章 grpc入门示例 第三章 proto文件数据类型 第四章 多服务示例 第五章 多proto文件示例 第六章 服务器流式传输 文章目录 一、proto语法二、proto中的标量类型三、proto中的数组类型四、proto中的Map类型五、proto中的嵌套类型六、…

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&…

【基础知识10】label与input标签

label标签说明 HTML元素表示用户界面中某个元素的说明 将一个和一个元素相关联主要有这些优点&#xff1a; 标签文本不仅与其相应的文本输入元素在视觉上相关联&#xff0c;程序中也是如此。这意味着&#xff0c;当用户聚焦到这个表单输入元素时&#xff0c;屏幕阅读器可以读…

柯桥商务英语培训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=

【LinuxC语言】网络编程中粘包问题

文章目录 前言什么叫做粘包问题粘包问题如何解决?总结前言 在进行网络编程时,我们经常会遇到一个非常常见但又往往被忽视的问题,那就是"粘包"问题。粘包是指在基于TCP/IP协议的数据传输过程中,由于TCP/IP协议是基于字节流的,这就可能会导致多个数据包被一起接收…

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时间处理工具类

效果 最近7天&#xff1a;2024年6月21日-2024年6月27日过去一周、最近一周&#xff1a;2024年6月16日-2024年6月22日过去三个月&#xff1a;2024年3月-2024年6月近半年、过去半年&#xff1a;2023年12月-2024年6月去年&#xff1a;2023年1月-2023年12月过去3年&#xff1a;202…

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

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

Spring Boot中的应用配置文件管理

Spring Boot中的应用配置文件管理 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨Spring Boot中的应用配置文件管理。在现代的软件开发中&am…

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;国内大模型全面领先。 大模…

【SpringBoot Web框架实战教程】04 SpringBoot 规范统一输出 json

不积跬步&#xff0c;无以至千里&#xff1b;不积小流&#xff0c;无以成江海。大家好&#xff0c;我是闲鹤&#xff0c;微信&#xff1a;xxh_1459&#xff0c;十多年开发、架构经验&#xff0c;先后在华为、迅雷服役过&#xff0c;也在高校从事教学3年&#xff1b;目前已创业了…