Studying-代码随想录训练营day26| 491.递增子序列、46.全排列、47.全排列 II、51.N皇后、37.解数独、回溯总结

第26天,回溯part04,昨天休息复习总结回溯内容,💪(ง •_•)ง💪

目录

491.递增子序列

46.全排列 

47.全排列 II

51.N皇后

37.解数独

回溯总结


491.递增子序列

文档讲解:代码随想录递增子序列

视频讲解:手撕递增子序列

题目:

学习:本题同属于子集问题,但不同的在于给了多个限制:1.子序列必须包含两个元素以上;2.子序列必须是递增的;3.不能对数组进行排序,即不能改变各元素之间的位置关系。

同时本题还需要进行去重,由于不能对数组进行排序,因此不能采用之前的去重办法,可以通过设置一个哈希表的方式进行去重。

代码:

//时间复杂度O(n*2^n)
//空间复杂度O(n)
class Solution {
public:vector<vector<int>> result; //返回数组vector<int> path; //记录路径//确定返回值和参数列表,在返回数组中直接进行操作所以不需要返回值,设置startindex确定每一层遍历范围void backtracking (vector<int>& nums, int startindex) {//收取路径大于1的节点if(path.size() > 1) {result.push_back(path);}//确定返回条件(在子集问题中可以不写)因为当startindex==nums.size()时,不会进入下面的循环if(startindex == nums.size()) {return;}//确定单层递归逻辑unordered_set<int> used; //使用哈希表记录已经使用的数字for(int i = startindex; i < nums.size(); i++) {//查找当前遍历的数字是否符合要求: 1.当前数字是否大于path中最后一个数字;2.当前数字是否没有遍历过if(!path.empty() && nums[i] < path.back() || (used.find(nums[i]) != used.end())) {continue; //进行下一轮循环}used.insert(nums[i]);//复合我们需要的数字条件path.push_back(nums[i]);backtracking(nums, i + 1);path.pop_back(); //回溯}}vector<vector<int>> findSubsequences(vector<int>& nums) {backtracking(nums, 0);return result;}
};

注意:本题规定了数值的范围在[-100,100]之间,数量一定且有序,因此本题也可以使用数组作为哈希表,能够提高算法效率。程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且每次重新定义set,insert的时候其底层的符号表也要做相应的扩充,也是费事的。

代码:

class Solution {
public:vector<vector<int>> result; //返回数组vector<int> path; //记录路径//确定返回值和参数列表,在返回数组中直接进行操作所以不需要返回值,设置startindex确定每一层遍历范围void backtracking (vector<int>& nums, int startindex) {//收取路径大于1的节点if(path.size() > 1) {result.push_back(path);}//确定返回条件(在子集问题中可以不写)因为当startindex==nums.size()时,不会进入下面的循环if(startindex == nums.size()) {return;}//确定单层递归逻辑int used[201] = {0}; //使用数组来记录数字是否使用过,因为题干规定数字在[-100, 100]之间。for(int i = startindex; i < nums.size(); i++) {//查找当前遍历的数字是否符合要求: 1.当前数字是否大于path中最后一个数字;2.当前数字是否没有遍历过if(!path.empty() && nums[i] < path.back() || (used[nums[i] + 100] == 1)) {continue; //进行下一轮循环}used[nums[i] + 100] = 1;//复合我们需要的数字条件path.push_back(nums[i]);backtracking(nums, i + 1);path.pop_back(); //回溯}}vector<vector<int>> findSubsequences(vector<int>& nums) {backtracking(nums, 0);return result;}
};

46.全排列 

文档讲解:代码随想录全排列

视频讲解:手撕全排列

题目:

学习:回溯算法解决第四类排列问题,排列问题的特点在于:1.在叶子结点收获结果;2.每层循环需从i = 0开始。因为对于排列问题来说[1,2]与[2,1]是不同的排列方式。

基于以上两点,排列不能使用startindex确定循环范围,而是需要一个used数组来标识每个元素是否被使用。

代码:

//时间复杂度O(n!)
//空间复杂度O(n)
class Solution {
public://排列问题vector<vector<int>> result; //返回数组vector<int> path; //记录路径//确定返回值和参数列表,在返回数组中直接操作,因此不需要返回值,与组合问题不同,排列存在顺序因此需要用一个数组来记录循环范围void backtracking(vector<int>& nums, vector<bool>& used) {//确定终止条件,在叶子节点处收获结果if(path.size() == nums.size()) {result.push_back(path);return;}//确定单层递归逻辑,每次都得从0开始,由used数组来确定遍历的范围for(int i = 0; i < nums.size(); i++) {//如果当前数遍历过,则跳过继续往后遍历if(used[i] == true) {continue;}//如果没有遍历过,进入单层递归逻辑used[i] = true;path.push_back(nums[i]);backtracking(nums, used);//回溯path.pop_back();used[i] = false;}}vector<vector<int>> permute(vector<int>& nums) {//设置一个bool类型的数组,记录每次遍历过程中该数有没有被使用,事实上这种方法,在前面的去重中也能够使用vector<bool> used(nums.size(), false);backtracking(nums, used);return result;}
};

47.全排列 II

文档讲解:代码随想录全排列II

视频讲解:手撕全排列II

题目:

学习:本题与上一题不同在于,本题存在重复数字需要进行去重处理。由于我们设置了used数组因此可以直接使用used数组进行去重处理。原理在于:当后面出现与前面相同的数字时,且前面的数字没有被使用(used == false),此时跳过当前数字。因为for循环顺序的原因,前面的数字一定先完成所有可能性的遍历,因此遍历到后面的数字,前面的数字没有被使用,说明已经遍历完成了,这种去重也属于树层去重。(注意:使用这种方式,需要对数组先进行排序)

代码:

//时间复杂度O(n!*n)
//空间复杂度O(n)
class Solution {
public://本题与前一题的不同之处在于需要进行去重vector<vector<int>> result; //返回数组vector<int> path; //记录路径//确定返回值和参数列表,本题与上一题不同在于去重,但使用used数组就能够进行去重处理,因此参数列表相同void backtracking(vector<int>& nums, vector<bool>& used) {//确定终止条件if(path.size() == nums.size()) {result.push_back(path);return;}//确定单层递归逻辑for(int i = 0; i < nums.size(); i++) {//首先进行数层去重,如果后面的数和前面的数相同,且前面的数已经完成了遍历回溯为false,则跳过if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {continue;}//接着判断当前遍历的数是否被使用if(used[i] == true) {continue;}//进入单层递归逻辑used[i] = true;path.push_back(nums[i]);backtracking(nums, used);//回溯path.pop_back();used[i] = false;}}vector<vector<int>> permuteUnique(vector<int>& nums) {//建立一个used数组,记录数是否使用vector<bool> used(nums.size(), false);//对nums排序便于去重sort(nums.begin(), nums.end());backtracking(nums, used);return result;}
};

51.N皇后

文档讲解:代码随想录N皇后

视频讲解:手撕N皇后

题目:

学习: 回溯算法解决第五类棋盘问题。棋盘问题的特点在于需要遍历的不再是一维数组,而是需要遍历二维数组。但本质仍是一层递归一层循环,对于本题来说每层循环需要在一行插入一个皇后,同时插入的皇后有三个约束条件:1.不能同行;2.不能同列;3.不能同斜线。基于此也可以将棋盘问题的搜索过程抽象为一棵树:

注意:棋盘问题一般也是在叶子结点收获结果,由于存在插入是否可行的判断,因此只要搜索到了树的叶子结点,就说明找到了皇后们的合理位置了。

代码:本题关键在于理解棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度,这样就可以套进回溯法的模板里了。

//时间复杂度O(n!)
//空间复杂度O(n)
class Solution {
public://棋盘问题vector<vector<string>> result; //返回数组vector<string> chessboard; //棋盘也是每一个结果//确定返回值和参数列表,循环遍历的是列,因此加入n和当前遍历的行void backtracking(int n, int row) {//确定终止条件,在叶子结点收获结果,能够走到叶子结点说明一定正确if(row == n) {result.push_back(chessboard);return;}//确定单层递归逻辑,对列进行循环遍历for(int i = 0; i < n; i++) {//判断当前列能不能插入皇后if(isvalid(n, row, i)) { //如果可以的话,进行下一层递归chessboard[row][i] = 'Q';backtracking(n, row + 1);//回溯chessboard[row][i] = '.';}}}//参数分别表示n,当前行数,当前列数bool isvalid(int n, int row, int col) {//判断是否冲突:注意本题不需要判断行是否冲突,因为在for循环遍历过程中我们只允许一行有一个皇后//判断列是否冲突,后面的行还没有插入皇后,所以只需要遍历到row就可以for(int i = 0; i < row; i++) {if(chessboard[i][col] == 'Q') return false;}//判断45°角位置有没有皇后for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {if(chessboard[i][j] == 'Q') return false;}//判断135°角位置有没有皇后for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {if(chessboard[i][j] == 'Q') return false;}return true;}vector<vector<string>> solveNQueens(int n) {//对chessboard进行初始化处理for(int i = 0; i < n; i++) {chessboard.push_back(string(n,'.'));}backtracking(n, 0);return result;}
};

37.解数独

文档讲解:代码随想录解数独

视频讲解:手撕解数独

题目:

学习:本题也属于棋盘问题,本题也需要在二维数组中插入元素,并且不断判断插入的是否合理。 但与N皇后不同的在于,本题一行不仅仅插入一个元素,而是需要插入多个元素,需要对行和列都进行遍历。因此本题一层递归不止一个循环,一层递归包含两个循环,因此本题要做的是二维递归。

本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。用部分树来表示为:

注意:本题不需要遍历所有的结果,只需要找到一个结果就可以返回,因此本题的返回值不再是void,而是bool,这样能够在找到一个答案之后立马进行返回。

代码:

class Solution {
public://棋盘问题——二维回溯char arr[9] = {'1','2','3','4','5','6','7','8','9'}; //设置1-9的字符数组方便遍历//确定返回值和参数列表,本题返回值需要为bool类型,因为,本题不需要遍历所有的结果,只要找到一个结果就返回即可,因此选择bool类型bool backtracking(vector<vector<char>>& board) {//确定终止条件,本题需要终止条件,因为本题中间会遍历错误会进行返回,如果遍历到最后也无需判断当前情况。//确定单层递归逻辑——两层循环//遍历行for(int i = 0; i < board.size(); i++) {//遍历列for(int j = 0; j < board.size(); j++) {if(board[i][j] != '.') continue; //找到第一个空格点//进行插入数字尝试for(char k : arr) {//判断当前位置是否可以插入if(isvalid(board, i, j, k)) { //可以插入的话进入下一层循环board[i][j] = k;//当后面的结果回传回来if(backtracking(board)) return true;board[i][j] = '.'; //回溯}}//当前位置1-9都不行,说明无法得到答案return false;}}//完成所有的遍历return true;}//判断函数,参数列表分别为,原数组,行,列,插入的元素bool isvalid(vector<vector<char>>& board, int row, int col, char val) {//判断行是否重复for(int j = 0; j < board.size(); j++) {if(board[row][j] == val) return false;}//判断列是否重复for(int i = 0; i < board.size(); i++) {if(board[i][col] == val) return false; }//判断九宫格内是否有重复//确定九宫格位置int startrow = row / 3 * 3, startcol = col / 3 * 3;for(int i = startrow; i < startrow + 3; i++) {for(int j = startcol; j < startcol + 3; j++) {if(board[i][j] == val) return false;} }return true;}void solveSudoku(vector<vector<char>>& board) {backtracking(board);}
};

回溯总结

文档讲解:代码随想录回溯总结

回溯算法本质是一个暴力搜索的算法,并不是什么高效的算法。

回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。

回溯算法一般能够解决5类问题:

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

回溯算法的过程,可以抽象为树形结构,因此解题的时候,可以通过画树形结构辅助做题。

回溯算法三部曲模版:

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

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

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

相关文章

[数据库原理]数据库设计(er图)

xtu期末是机试&#xff0c;所以图形表示有点不同 实体之间的关系&#xff1a; 多对多&#xff1a;可以生成一个新的关系模型一对一&#xff1a;两边都要关联一对多、多对一 &#xff1a;一的主键可以作为多的外键 如有错误&#xff0c;欢迎指正&#xff01;&#xff01;&#x…

中画幅巡检相机-SHARE 100M A10

【毫厘之间&#xff0c;洞见非凡】 ——SHARE 100M A10中画幅测量相机&#xff0c;巡检行业的新选择 在巡检行业&#xff0c;精准度是关键&#xff0c;深圳赛尔智控科技有限公司最新推出的SHARE 100M A10中画幅测量相机&#xff0c;基于先进的IMX461影像传感器&#xff0c;拥有…

rmvb转mp4格式有什么好方法?这四种好方法轻松帮你完成转换!

rmvb转mp4格式有什么好方法&#xff1f;当大家深入探索视频格式的多元世界时&#xff0c;不禁会被各种格式的独特魅力所吸引&#xff0c;在众多选项中&#xff0c;RMVB和MP4无疑是两大热门选择&#xff0c;它们各自在不同的场合下展现出了令人瞩目的优势与局限&#xff0c;首先…

25考研:今年初试时间比去年更早了?

过去5年考研初试时间安排如下&#xff1a; 24考研&#xff1a;2023年12月23-24日&#xff08;倒数第二个周末&#xff09; 23考研&#xff1a;2022年12月24-25日&#xff08;倒数第二个周末&#xff09; 22考研&#xff1a;2021年12月25-26日&#xff08;最后一个周末&#xf…

【Linux】TCP协议【下二】{流量控制/滑动窗口/延迟应答/捎带应答/拥塞控制}

文章目录 1.流量控制--利用“窗口大小”字段协商数据量大小1. 1第一次的时候&#xff0c;怎么保证发送数据量是合理的1.2第三次握手ack的时候&#xff0c;可以携带数据&#xff01;1.3流量控制&#xff0c;属于可靠性还是属于效率&#xff1f; 2.滑动窗口--利用滑动窗口解决批量…

快速入门FreeRTOS心得(正点原子学习版)

对于FreeROTS&#xff0c;我第一反应想到的就是通信里的TDM&#xff08;时分多址&#xff09;。不同任务给予分配不同的时间间隔&#xff0c;也就是任务之间在每个timeslot都在来回切换。 这里有重要的一点&#xff0c;就是中断要短小&#xff0c;优先级是自高到底进行打断。 …

Cocos制作抖音小游戏接入侧边栏复访接口实例

本篇文章主要讲解&#xff0c;使用cocos接入抖音小游戏侧边栏接口的实例教程。 日期&#xff1a;2024年7月1日 作者&#xff1a;任聪聪 教程实例&#xff1a;https://download.csdn.net/download/hj960511/89509196 下载后可直接导入运行 上传游戏后抖音预审不通过 注意&#x…

98 - IDEA远程调试服务器Java程序

Java 提供了一套标准的调试协议&#xff08;JDWP - Java Debug Wire Protocol&#xff09;&#xff0c;允许调试器&#xff08;IDE&#xff09;与被调试程序&#xff08;应用&#xff09;之间进行通信。 1.服务器特定命令启动程序 在服务器上以以下命令启动Java程序 java -a…

南京林业大学点云相关团队论文

【1】Chen Dong, Wan Lincheng, Hu Fan, Li Jing, Chen Yanming, Shen Yueqian*, Peethambaran Jiju, 2024. Semantic-aware room-level indoor modeling from point clouds, International Journal of Applied Earth Observation and Geoinformation, 2024, 127, 103685. 语义…

什么是脏读、幻读、不可重复读

数据库事务 数据库事务是指作为单个逻辑工作单元执行的一系列操作&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部失败回滚&#xff0c;以保持数据库的一致性和完整性。在多线程或多用户同时操作时&#xff0c;难免会出现错乱与冲突&#xff0c;这就需要引入事务的…

软考高级-系统分析师知识点100条速记!

宝子们&#xff01;上半年软考已经结束一段时间了&#xff0c;准备备考下半年软考高级-系统分析师的小伙伴可以开始准备了&#xff0c;毕竟高级科目的难度可是不低的&#xff0c;相信参加过上半年系分的小伙伴深有体会。 这里给大家整理了100条系分知识点&#xff0c;涵盖全书9…

面试官:你了解git cherry-pick吗

事情要从一次不规范的代码开发开始说起 背景故事 时间 2024年某个风平浪静的周五晚上 地点 中国&#xff0c;北京&#xff0c;西二旗&#xff0c;某互联网大厂会议室 人物 小杰&#xff0c;小A&#xff0c;小B&#xff0c;老K 对话 老K&#xff1a;昨天提交的代码被测试打回来…

[ROS 系列学习教程] 建模与仿真 - 使用 ros_control 控制差速轮式机器人

ROS 系列学习教程(总目录) 本文目录 一、差速轮式机器人二、差速驱动机器人运动学模型三、对外接口3.1 输入接口3.2 输出接口 四、控制器参数五、配置控制器参数六、编写硬件抽象接口七、控制机器人移动八、源码 ros_control 提供了多种控制器&#xff0c;其中 diff_drive_cont…

方法的用法

一.简介 目前为止我给出的所有的案例都是将代码放在main方法中&#xff0c;就会产生一些问题&#xff1a; 代码冗长&#xff0c;不利于维护变量过多&#xff0c;想不出那么多的变量名没有重用性 那么该如何解决呢&#xff1f; 我们可以编写功能性的代码块&#xff0c;来被ma…

FormMaking表单设计器V3.8发布,数据表格上线,支持多选、多级表头、列模板自定义、操作列、分页等设置

介绍 FormMaking 是基于Vue的可视化表单设计器&#xff0c;赋能企业实现可视化低代码开发模式&#xff1b;帮助开发者从传统枯燥的表单代码中解放出来&#xff0c;更多关注业务&#xff0c;快速提高效率&#xff0c;节省研发成本。 目前已经在OA系统、考试系统、报表系统、流程…

MyBatis-plus这么好用,不允许还有人不会

你好呀&#xff0c;我是 javapub. 做 Java 的同学都会用到的三件套&#xff0c;Spring、SpringMV、MyBatis。但是由于使用起来配置较多&#xff0c;依赖冲突频发。所有&#xff0c;各路大佬又在这上边做了包装&#xff0c;像我们常用的 SpringBoot、MyBatisPlus。 基于当前要…

C语言的数据结构:图的操作

&#x1f6fa;图的遍历&#xff1a; 注意&#xff1a;在遍历的过程中&#xff0c;可能会出现 回路 ( 已经访问过的节点还要重新访问一次 ) \color{orange}回路(已经访问过的节点还要重新访问一次) 回路(已经访问过的节点还要重新访问一次). 当从A开始访问时&#xff0c;先访问…

heic格式转化jpg,手把手教你将heic转换成jpg【办公必备】

一、什么是heic heic格式是一种高效的图片格式&#xff0c;它可以在较小的文件大小下提供高质量的图片。 二、如何打开heic 然而&#xff0c;这种图片因其格式的特殊性&#xff0c;在实际应用中仍存在一些问题&#xff1a;压缩效果可能不够理想&#xff0c;一些老旧的软件和设…

stm32学习笔记---USART串口外设(理论部分)

目录 USART简介 USART的框图 串口的引脚 USART的基本结构 数据帧 起始位侦测 数据采样 波特率发生器 USD转串口模块的原理图 声明&#xff1a;本专栏是本人跟着B站江科大的视频的学习过程中记录下来的笔记&#xff0c;我之所以记录下来是为了方便自己日后复习。如果你…

TypeScript 中 const enum 和 enum 的核心区别在哪?日常开发应该使用哪个?

编译结果 enum 会生成一个对象&#xff0c;引用的地方保持对其引用 const enum 会擦除 enum 定义的代码&#xff0c;引用的地方会生成 inline code 使用enum&#xff1a; 使用const enum&#xff1a; PS&#xff1a;编译选项 preserveConstEnums 可以使 const enum 不去擦除 …