代码随想录算法训练营第二十二天(回溯 一)

开始学习回溯!

回溯理论基础

代码随想录文章链接:代码随想录

文章摘要:

什么是回溯法

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。

在二叉树系列中,我们已经不止一次,提到了回溯。

回溯是递归的副产品,只要有递归就会有回溯。

所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数

回溯法的效率

回溯法的性能如何呢,这里要和大家说清楚了,虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法

因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

那么既然回溯法并不高效为什么还要用它呢?

因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。

此时大家应该好奇了,都什么问题,这么牛逼,只能暴力搜索。

回溯法解决的问题

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

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

回溯法模板

在前段时间的二叉树学习中我们说了递归三部曲,这里我再给大家列出回溯三部曲

  • 回溯函数模板返回值以及参数

回溯算法中函数返回值一般为void。

再来看一下参数,因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。

回溯函数伪代码如下:

void backtracking(参数)
  • 回溯函数终止条件

那么我们在讲解递归三部曲的时候,就知道递归函数一定要有终止条件,不能无限的递归下去。

回溯也一样,要有终止条件。

什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。

所以回溯函数终止条件伪代码如下:

if (终止条件) {存放结果;return;
}
  • 回溯搜索的遍历过程

在上面我们提到了,回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

如图:

回溯算法理论基础

注意图中,我特意举例集合大小和孩子的数量是相等的!

回溯函数遍历过程伪代码如下:

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

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

backtracking这里自己调用自己,实现递归。

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

然后结合上面的思路,我们可以写出下面这个伪代码模板:

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

看完这些,我们就开始刷题吧! 

力扣题部分:

77. 组合

题目链接:. - 力扣(LeetCode)

题面:

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

思路:

本题是回溯法的经典题目。

直接的解法当然是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果,下面k = 2时的代码。

int n = 4;
for (int i = 1; i <= n; i++) {for (int j = i + 1; j <= n; j++) {cout << i << " " << j << endl;}
}

如果 k = 3 呢?

我们就需要三个for循环。

如果n为100,k为50呢?那就50层for循环,是不是开始窒息

此时就会发现虽然想暴力搜索,但是用for循环嵌套连暴力都写不出来!

回溯搜索法来了,虽然回溯法也是暴力,但至少能写出来,不像for循环嵌套k层让人绝望。

那么回溯法怎么暴力搜呢?

上面我们说了要解决 n为100,k为50的情况,暴力写法需要嵌套50层for循环,那么回溯法就用递归来解决嵌套层数的问题

递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了

此时递归的层数大家应该知道了,例如:n为100,k为50的情况下,就是递归50层。

如果n = 4, k = 2,回溯思路应该就是这样的: 

大致看懂这个图,接下来就让我们开始回溯三部曲

回溯函数模板返回值以及参数

代码如下:

void backtracking(int n, int k, int startIndex)

n 和 k 都没什么问题,startindex是什么呢?

startindex是保证回溯过程不会重复的一个标志。

题目的意思是{1,2}和{2,1}是同一种组合,如果我们只弄for循环没有startindex,会导致组合重复,看看下面这个for循环,如果没有startindex,i从0开始,显然会重复。

for(int i = startIndex; i <= n; i ++)

除此之外,我们还要创建两个全局变量,一个用来存放符合条件单一结果(如{1,2}),一个用来存放符合条件结果的集合。

代码如下:

vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果

回溯函数终止条件

遍历过程中如果path的长度和k相等,意味着当前我们找到的组合符合条件了,这个时候我们就要把path记录下来,记录给result然后再结束本次递归。

终止条件代码如下:

if (path.size() == k) {result.push_back(path);return;
}

回溯搜索的遍历过程

其实遍历的循环外壳上面已经写过了。

for里面的内容需要注意——递归前加进来的元素需要在递归后去除。

整体代码如下:

for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历path.push_back(i); // 处理节点backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始path.pop_back(); // 回溯,撤销处理的节点
}

写到这里,代码整合一下就出来了。

我们可以结合模板看下面的代码,基本就是套了模板的壳。

代码实现:

class Solution {
public:vector<int>path;vector<vector<int>>result;void backtracking(int n, int k, int startIndex){if(path.size() == k){result.push_back(path);return;}for(int i = startIndex; 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;}
};

216.组合总和III

题目链接:. - 力扣(LeetCode)

题面:

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

思路:

上面讲了那么多,这道题就不重复了,直接回溯三部曲:

回溯函数模板返回值以及参数

和上面相比,我们需要一个通过sum来判断和是不是n来决定是否符合条件。

当然,两个全局变量是不可少的。

vector<int>rightnums;
vector<vector<int>>result;
void find(int n, int k, int sum, int index)

回溯函数终止条件

如果长度等于k,就该停止操作了,当然停止前要判断是否符合条件,符合得记得加入答案的数组。

if(rightnums.size() == k)
{if(sum == n) result.push_back(rightnums);return;
}

回溯搜索的遍历过程

这回的集合元素只有1-9,所以for循环的i从index到9就行了。遍历如图所示

处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。

代码如下:

for (int i = startIndex; i <= 9; i++) {sum += i;path.push_back(i);backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndexsum -= i; // 回溯path.pop_back(); // 回溯
}

别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!

整合一下上面的代码答案就出来了。

代码实现:

class Solution {
public:vector<int>rightnums;vector<vector<int>>result;void find(int n, int k, int sum, int index){if(rightnums.size() == k){if(sum == n) result.push_back(rightnums);return;}for(int i = index; i <= 9; i ++){rightnums.push_back(i);sum += i;find(n, k, sum, i + 1);sum -= i;rightnums.pop_back();}}vector<vector<int>> combinationSum3(int k, int n) {find(n, k, 0, 1);return result;}
};

17.电话号码的字母组合

题目链接:. - 力扣(LeetCode)

题面:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。对应关系如图所示。

思路:

回溯三部曲:

回溯函数模板返回值以及参数

vector<string>result;string mapnums[10] = {"6", "6","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};string ans = "";

把映射的内容记录成字符串数组放在全局变量的位置(就是下面代码的mapnums),然后还是两个全局变量,一个是放当前的组合,一个记录所有符合条件组合的答案。

回溯函数终止条件

if(ans.size() == digits.size()) 
{result.push_back(ans);return;
}

因为这个组合通过一一映射得到,显然当前组合的长度和字符串digits的长度相同时记录下来并结束当前递归函数。

回溯搜索的遍历过程

int k, i, d;
k  = ans.size();
d = digits[k] - '0';
string s;
for(i = 0; i < mapnums[d].size(); i ++)
{s = mapnums[d][i];ans += s;backtracking(digits);ans.pop_back();
}

这回的for循环要看数字对应的映射字符串数组mapnums,数字不同遍历对象也不同。

我们通过k记录当前的组合长度,那digits[k]就刚好是读取的当前数字,由于数字是字符char格式,转int需要记得减去字符0。

s代表的是读取数字可能的字符,根据mapnums和当前数字d确定映射的具体字符串,s就是字符串的每种可能性。

这样的遍历下来,每种情况都可以通过这个回溯递归函数穷举得到。

代码实现:

class Solution {
public:vector<string>result;string mapnums[10] = {"6", "6","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};string ans = "";void backtracking(string digits){if(ans.size() == digits.size()) {result.push_back(ans);return;}int k, i, d;k  = ans.size();d = digits[k] - '0';string s;for(i = 0; i < mapnums[d].size(); i ++){s = mapnums[d][i];ans += s;backtracking(digits);ans.pop_back();}}vector<string> letterCombinations(string digits) {if(digits.size() == 0) return result;backtracking(digits);return result;}
};

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

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

相关文章

屏幕翻译器下载哪个?语言达人必备这些

想象一下&#xff0c;你站在人头攒动的12分钟洛杉矶沙滩音乐节现场&#xff0c;四周是来自世界各地的音乐爱好者&#xff0c;他们带着各自的文化与热情&#xff0c;用不同的语言欢呼、交谈。 舞台上&#xff0c;乐队正激情演奏&#xff0c;旋律激荡人心&#xff0c;但偶尔传来…

HarmonyOS 开发

环境 下载IDE 代码 import { hilog } from kit.PerformanceAnalysisKit; import testNapi from libentry.so; import { router } from kit.ArkUI; import { common, Want } from kit.AbilityKit;Entry Component struct Index {State message: string Hello HarmonyOS!;p…

AI赋能软件测试:从自动化到智能化,让测试工作事半功倍

引言 在当今这个日新月异的数字时代&#xff0c;人工智能&#xff08;AI&#xff09;正以不可阻挡之势渗透并重塑着各行各业&#xff0c;其中&#xff0c;软件开发与测试领域更是迎来了前所未有的变革。随着软件系统的复杂性日益增加&#xff0c;用户对软件质量、性能及安全性的…

SQL每日一练-0816

今日SQL题&#xff1a;计算每个项目的年度收入增长率 难度系数&#xff1a;&#x1f31f;☆☆☆☆☆☆☆☆☆ 1、题目要求 计算每个项目每年的收入总额&#xff0c;并计算项目收入环比增长率。找出每年收入增长率最高的项目。输出结果显示年份、项目ID、项目名称、项…

微软AI人工智能认证有哪些?

微软提供的人工智能认证主要包括以下几个方面&#xff1a; Azure AI Fundamentals&#xff08;AI900认证&#xff09;&#xff1a;这是一个基础认证&#xff0c;旨在展示与Microsoft Azure软件和服务开发相关的基本AI概念&#xff0c;以创建AI解决方案。它面向具有技术和非技术…

[数据集][目标检测]航拍屋顶检测数据集VOC+YOLO格式458张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;458 标注数量(xml文件个数)&#xff1a;458 标注数量(txt文件个数)&#xff1a;458 标注类别…

【EI检索稳定】2024年第四届数字化社会与智能系统国际学术会议(DSInS 2024)

由悉尼科技大学和西南交通大学联合主办&#xff0c;四川大学、中南大学社会计算研究中心、西南财经大学、武汉理工大学协办的2024年第四届数字化社会与智能系统国际学术会议将于2024年11月22-24日在中国郑州举行。会议主题主要聚焦智能系统在数字化社会中的相关技术和应用发展。…

Vsphere连接ESXI主机创建虚拟机并安装操作系统

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

Nature系列|病理人工智能综述以及模型的可解释性分析|顶刊速递·24-08-21

小罗碎碎念 文献日推主题&#xff1a;病理AI综述&模型可解释性分析 今天准备了四篇文章&#xff0c;感觉之前一次推六篇&#xff0c;多了点&#xff0c;以后都这个标准了。 前三篇都是nature reviews系列的大综述&#xff0c;其中两篇是不区分癌种的&#xff0c;第三篇是专…

FTP协议-匿名用户登录 从0到1

前言 日常大家可能接触web漏洞比较多而对其他端口及协议不那么了解&#xff0c;其实其他协议漏洞在渗透中也同样重要只是平时可能接触得不多。本文将介绍FTP协议、FTP匿名用户登录及其具体流程分析和自动化利用demo。 FTP简介 FTP是File Transfer Protocol&#xff08;文件传…

MIAOYUN与CStack签署总代协议,共拓一体化云端交互管理市场!

在深刻洞察国内云原生技术日新月异的发展态势&#xff0c;并秉持着共谋市场蓝海、深化战略协同的高度共识下&#xff0c;成都元来云志科技有限公司&#xff08;简称“MIAOYUN”&#xff09;与上海酷栈科技有限公司&#xff08;简称“CStack”&#xff09;于近期签署了总代理合作…

SpringBoot项目多线程实现定时任务-只需要三步

众所周知&#xff0c;项目中需要使用定时任务发布的需求时非常常见的&#xff0c;例如&#xff1a;数据同步&#xff0c;清理垃圾文件&#xff0c;清理过期用户等需求&#xff0c;可能需要我们定时去清理数据。 但是我们如果集成xxl-job&#xff0c;Quartz&#xff0c;spring …

【C语言小项目】五子棋游戏

目录 前言 一、游戏规则 1.功能分析 2.玩法分析 3.胜负判定条件 二、游戏实现思路 三、代码实现与函数封装 1.项目文件创建 2.头文件说明 3.函数封装 1&#xff09;菜单实现 2&#xff09;进度条实现 3&#xff09;main函数实现 4&#xff09;Game函数 5&#xff0…

【机器学习】小样本学习的实战技巧:如何在数据稀缺中取得突破

我的主页&#xff1a;2的n次方_ 在机器学习领域&#xff0c;充足的标注数据通常是构建高性能模型的基础。然而&#xff0c;在许多实际应用中&#xff0c;数据稀缺的问题普遍存在&#xff0c;如医疗影像分析、药物研发、少见语言处理等领域。小样本学习&#xff08;Few-Shot Le…

聚观早报 | 12306推出两项新功能;苹果音乐限时免费试用

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 8月22日消息 12306推出两项新功能 苹果音乐限时免费试用 iQOO 13将采用标志性灯带 Redmi K80 Pro渲染图曝光 vi…

C#实现数据采集系统-多设备采集

系统功能升级-多设备采集 数据采集系统在网络环境下&#xff0c;性能足够&#xff0c;可以实现1对多采集&#xff0c;需要支持多个设备进行同时采集功能&#xff0c;现在就开发多设备采集功能 修改多设备配置 设备配置 将DeviceLink 改成List集合的DeviceLinks删掉Points&a…

Vscode——如何实现 Ctrl+鼠标左键 跳转函数内部的方法

一、对于Python代码 安装python插件即可实现 二、对于C/C代码 安装C/C插件即可实现

【MySQL进阶之路】数据的查询

目录 建表 全列查询 指定列查询 查询表达式 指定别名 结果去重 WHERE 条件查询 模糊查询 结果排序 筛选分页结果 不同子句的执行顺序 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 建表 CREATE TABLE grades( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name …

前端技巧——复杂表格在html当中的实现

应用场景 有时候我们的表格比较复杂&#xff0c;表头可能到处割裂&#xff0c;我们还需要写代码去完成这个样式&#xff0c;所以学会在原生html处理复杂的表格还是比较重要的。 下面我们来看这一张图&#xff1a; 我们可以看到有些表头项的规格不太一样&#xff0c;有1*1 2*…

雅菲奥朗 FinOps 认证培训:开启企业云财务管理转型之路

前言&#xff1a; 在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的IT财务挑战。随着云计算和数字化转型的推进&#xff0c;传统的财务管理方式已经不能满足“企业上云”的需求。FinOps&#xff0c;即“云财务管理”应运而生&#xff0c;成为帮助企业实现IT财务流…