算法练习第20天|回溯算法 77.组合问题 257. 二叉树的所有路径

1.什么是回溯算法?

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。其本质是穷举,穷举所有可能,然后选出我们想要的答案。

2.为什么要有回溯算法?

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

因为有的问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。比如下面这几类问题:

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

 3.如何理解回溯算法?

回溯法解决的问题都可以抽象为树形结构,是的,是所有回溯法的问题都可以抽象为树形结构!

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。所以回溯和递归是分不开的

递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

4.回溯算法模板 

类似递归算法的三部曲,回溯算法也有三部曲。

  • 第一步:确认回溯函数的参数及返回值。返回值类型一般为void。但是参数不想二叉树递归那样好确定,所以一般先写逻辑,根据回溯代码逻辑的需要再添加相应参数。回溯函数大致长这样:
void backtracking(参数)
  • 第二步:确认回溯函数的终止条件。既然回溯函数的问题可以等效为树形结构,那么就会遍历树形结构就一定会有终止条件。因此回溯也有终止条件。一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。所以终止条件的伪代码如下:
if (终止条件) {存放结果;return;
}
  • 第三步:确认单层回溯的遍历过程

由于回溯一般是在集合中进行递归搜索,集合的大小构成了树的宽度,递归的深度构成了树的深度。如图所示:

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

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

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

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

 整体框架如下:

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

 力扣题目77.组合

77. 组合 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/combinations/description/

题目描述:

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

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

示例 1:

输入:n = 4, k = 2
输出:
[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

思路分析:

最直接的方法就是简单粗暴的双层循环(假设n=4,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为2时就只用双层循环就行了,但是如果k=50,100?自己要写50层、100层循环就不太现实了。所以这个时候就可以使用回溯了。过程示意如图所示:

图中可以发现n相当于树的宽度,k相当于树的深度

那么如何在这个树上遍历,然后收集到我们要的结果集呢?

图中每次搜索到了叶子节点,就找到了一个结果

相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。

回溯解法:

来遍历的过程中,会找到复合要求的子集合,还要有记录这些子集合的结果大集合,所以下定义这两个记录结果的vector:

vector<vector<int>> result;  //用于存放组合的集合
vector<int> path; //用于存放复合条件的当前组合

 下面按照回溯三部曲来进行回溯函数的实现:

  • 第一步,确认回溯函数的参数以及返回值。

        返回值为void,函数的参数为n,k。初次之外,为了更有逻辑的进行穷举,我们再设置一个startIndex,表示本次回溯从【1,2,..., n】的哪里开始遍历元素。所以回溯函数长这样:

void backtracking(int n, int k, int startIndex){
}
  •  第二步,确认回溯的终止条件。根据题意,当记录当前组合的path有了k个元素,即本次回溯就可以终止了,要保存当前结果然后返回。

        具体回溯终止条件长这样: 

//回溯第二步:确认回溯函数的终止条件
if(path.size() == k)  //取得一个k长的组合
{result.push_back(path);return;
}
  •  第三步,确认单层回溯函数的遍历过程,即再回溯中需要做哪些事情?

 根据上述伪代码,需要做三件事:1.在当前层处理节点,即将没遍历过程的元素记录一下;2.递归,从刚刚记录过的元素的下一个元素继续进行该过程,直到条件满足代码返回该递归处;3.弹出刚才最后记录的元素,相当于组合的结果返回来到了上一层的分支处(即通过绿色剪头回溯到示意图中的第二层):

 代码如下:

//我要从startIndex往后开始遍历,将得到的节点元素存放在path中
for(int i = startIndex; i <= n; ++i)
{//处理节点path.push_back(i);//递归回溯函数,开始找下一个元素并添加到path中backtracking(n, k, i+1);//回溯,返回上一层path.pop_back();
}

整体代码如下:

class Solution {
public:vector<vector<int>> result;  //用于存放组合的集合vector<int> path; //用于存放复合条件的组合//回溯第一步:确认回溯函数的参数及返回值,//startIndex用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )void backtracking(int n, int k, int startIndex){//回溯第二步:确认回溯函数的终止条件if(path.size() == k)  //取得一个k长的组合{result.push_back(path);return;}    //回溯第三步:确认单层回溯的遍历过程。//我要从startIndex往后开始遍历,将得到的节点元素存放在path中for(int i = startIndex; i <= n; ++i){//处理节点path.push_back(i);//递归回溯函数,开始找下一个元素并添加到path中backtracking(n, k, i+1);//回溯,返回上一层path.pop_back();}}//力扣提供的接口函数vector<vector<int>> combine(int n, int k) {backtracking(n,k,1);return result;}
};

从上述代码来理解,回溯算法的第三步中的递归,会一直执行,直到满足组合满足要求,然后会逐层进行回溯。

同样的回溯思想也可以解下面这道题。 

257. 二叉树的所有路径

257. 二叉树的所有路径 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/binary-tree-paths/description/

题目描述:

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

示例 2:

输入:root = [1]
输出:["1"]

思路分析:

因为要记录根节点到叶子节点的路径,所以二叉树的遍历方式应该为前序遍历,这样才是正确的路径顺序。遍历和和回溯的过程如下图所示,数字表示先后步骤。

下面我们先使用递归的方式,来做前序遍历,然后在递归中使用回溯

递归+回溯解法:

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* cur, vector<int>& path, vector<string> & result){//中path.push_back(cur->val);// 递归终止条件:这才到了叶子节点if (cur->left == NULL && cur->right == NULL) {string sPath;for (int i = 0; i < path.size() - 1; i++) {sPath += to_string(path[i]);sPath += "->";}sPath += to_string(path[path.size() - 1]);result.push_back(sPath);return;}if (cur->left) { // 左 traversal(cur->left, path, result);path.pop_back(); // 回溯}if (cur->right) { // 右traversal(cur->right, path, result);path.pop_back(); // 回溯}}vector<string> binaryTreePaths(TreeNode* root) {vector<string> result;  //记录路径的集合vector<int> path;  //记录路径中的元素if (root == nullptr) return result;traversal(root, path, result);return result;}

不使用额外的递归函数的写法:

class Solution {
public:vector<string> result;  //记录路径的集合vector<int> path;  //记录路径中的元素//前序递归第一步:确认递归函数的参数和返回值vector<string> binaryTreePaths(TreeNode* root) {if (root == nullptr) return result;path.push_back(root->val);  //中,先记录一下当前节点元素//递归函数第二步:确认递归终止条件。找到叶子节点才算遍历结束if(root->left == nullptr && root->right == nullptr) {//一旦找到叶子节点,我们就需要打印该路径string singlePath;for(int i = 0; i < path.size()-1; i++)  //从前往后提取路径“1-》2-》3”{singlePath += to_string(path[i]);  //元素数字转字符串singlePath += "->";}singlePath += to_string(path[path.size()-1]); //最后一个元素result.push_back(singlePath);  //记录该路径return result;} //递归第三步:确认单层递归逻辑。处了记录当前节点元素,接下来就是递归遍历左右子树了。//但是为了更方便的生成结果所需的字符串,我们将记录当前节点的步骤放在了函数的开头。//如果我们在这里记录当前节点元素,那么上面的递归终止条件返回的路径结果将会缺少最后一个元素//左if(root->left){binaryTreePaths(root->left); //递归遍历左子树path.pop_back();  //回溯,返回上一层对应的根节点,准备向右子树遍历}//右if(root->right){binaryTreePaths(root->right); //递归遍历右子树path.pop_back();  //回溯}return result;}};

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

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

相关文章

Hive进阶(2)----HDFS写入数据流程(赋图助君理解)

HDFS写入数据流程 一、写入流程 1、 Client向NameNode发起RPC请求&#xff0c;来确定请求文件block所在的位置&#xff1b; 2、 NameNode会视情况返回文件的部分或者全部block列表&#xff0c;对于每个block&#xff0c;NameNode都会返回含有该block副本的DataNode地址&…

Ubuntu22.04.4 - 网络配置 - 笔记

一、设置固定ip 1、cd /etc/netplan 查看文件夹下的配置文件 我这里叫 00-installer-config.yaml 2、sudo nano /etc/netplan/00-installer-config.yaml 完成配置后&#xff0c;按下Ctrl O保存更改&#xff0c;然后按下Ctrl X退出nano编辑器。 3、sudo netplan apply 4、ip …

前端开发攻略---合并表格单元格,表格内嵌套表格实现手风琴效果。

1、演示 2、思路 1、用传统的 <table></table> 表格标签来实现比较麻烦。因此通过模拟 表格标签 的写法用<div></div>来实现 2、表头和表格列数是相同的&#xff0c;因此可以确定代码结构 <div class"table"><div class"head…

PotPlayer 图像截取

PotPlayer 图像截取 1. PotPlayer2. PotPlayer 下载2.1. PotPlayer 240305 3. 图像截取References 1. PotPlayer http://www.potplayercn.com/ PotPlayer 是 KMPlayer 原作者姜勇囍进入新公司 Daum 之后推出的&#xff0c;继承了 KMPlayer 所有的优点&#xff0c;拥有异常强大…

Flask项目在Pycharm中设置局域网访问

打开PyCharm导入本应用。点击Run标签中的Edit Configurations 其中Target type选择Script path&#xff0c;Target填入本项目中app.py的路径&#xff0c;Additional optional填入--host0.0.0.0(不要有空格)。 再重新运行项目&#xff0c;会观察到除了原本的http://127.0.0.1:50…

【EI会议征稿通知】2024年图像处理、机器学习与模式识别国际学术会议(IPMLP 2024)

2024年图像处理、机器学习与模式识别国际学术会议&#xff08;IPMLP 2024) 2024 International Conference on Image Processing, Machine Learning and Pattern Recognition 重要信息 大会官网&#xff1a;www.ipmlp.net&#xff08;点击参会/投稿/了解会议详情&#xff09;…

【赛题】2024年“华中杯”数模竞赛赛题发布

2024年"华中杯"数学建模网络挑战赛——正式开赛&#xff01;&#xff01;&#xff01; 赛题已发布&#xff0c;后续无偿分享各题的解题思路、参考文献&#xff0c;帮助大家最快时间&#xff0c;选择最适合是自己的赛题。祝大家都能取得一个好成绩&#xff0c;加油&a…

uiautomation、pytest、schedule实现桌面程序自动化(初级)02

一&#xff1a;安装uiAutomation 前置条件:安装python、pycharm 命令行安装 Pip install uiautomation2.0.17 #指定版本 二&#xff1a;安装辅助工具&#xff1a;inspect.exe和、Accessibility Insights For Windows定位元素工具 辅助工具介绍 步骤中提到…

Hive进阶(4)----MapReduce的计算过程(赋图助君理解)

MapReduce的计算过程 MapReduce是一种编程模型和处理大规模数据集的方法。它通常用于分布式计算环境中&#xff0c;能够将数据处理任务分解成独立的部分&#xff0c;分配给多台计算机进行并行处理。这个模型由Google提出&#xff0c;并在开源领域中得到了广泛的应用和实现。Map…

无法连接到MongoDB Atlas 的Cloud Database

打开Mongodb网页: 选择允许任何地址连接 连接成功

Docker容器嵌入式开发:在Ubuntu上配置RStudio与R语言、可视化操作

目录 一、dirmngr工具二、R环境安装与配置三、验证是否安装成功四、安装Rstudio五、可视化操作参考 以上是在Ubuntu 18.04上安装最新版本的R语言环境的步骤摘要。首先&#xff0c;通过添加CRAN镜像源并安装GPG密钥来配置软件源。然后&#xff0c;更新软件包列表并通过apt安装R语…

SQL --索引

索引 INDEX 伪列 伪装起来的列&#xff0c;不容易被看见&#xff0c;要特意查询才能看见 ROWNUM&#xff1a; 是对查询结果自动生成的一组连续的自然数序号。 SELECT emp.*,ROWNUM FROM emp例题&#xff1a;查询emp表中&#xff0c;前三个员工 SELECT * FROM * from emp w…

【创建型模式】建造者模式

一、建造者模式概述 建造者模式定义&#xff1a;将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同得表示。(对象创建型模式)。 建造者模式分析&#xff1a; 1.将客户端与包含多个部件得复杂对象得创建过程分离&#xff0c;客户端无需知道复杂对象…

【unity】【C#】游戏音乐播放和发布

今天我们来认识一下有关 unity 音乐的一些知识 我们先创建 AudioClips 文件夹&#xff0c;这个文件夹通常就是 unity 中存放音乐的文件夹&#xff0c;然后拖进音乐文件进去 这里为大家提供了两个音乐&#xff0c;有需要可以自取 百度网盘&#xff1a;https://pan.baidu.com/s…

RIP最短路实验(华为)

思科设备参考&#xff1a;RIP最短路实验&#xff08;思科&#xff09; 一&#xff0c;技术简介 RIP&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;是一种基于距离矢量的内部网关协议&#xff0c;工作原理是每个路由器周期性地向邻居路由器发…

React Ant Design 简单实现如何选中图片

效果&#xff1a; 代码&#xff1a; 定义的初始值和方法 const [selected, setSelected] useState(0); // 表示当前选中的图片索引const handleClick (index) > {if (selected index) {setSelected(null); // 如果点击的是已选中的图片&#xff0c;则取消选中状态} else…

JVM虚拟机(十)Java内存泄漏的排查思路

目录 一、可能产生内存泄露的地方二、复现堆内存泄漏三、如何排查堆内存问题&#xff1f;3.1 获取对内存快照 dump3.2 使用 Visual VM 去分析 dump 文件3.3 定位内存溢出问题 一、可能产生内存泄露的地方 在进行排查 Java 的内存泄漏问题之前&#xff0c;首先我们要知道哪里可…

【任务调度】Apache DolphinScheduler快速入门

Apache DolphinScheduler基本概念 概念&#xff1a;分布式、去中心化、易扩展的可视化DAG工作流任务调度系统。 作用&#xff1a;解决数据处理流程中错综复杂的依赖关系&#xff0c;使调度系统在数据处理流程中开箱即用。Apache DolphinScheduler是一款开源的调度工具&#xff…

windows下python opencv ffmpeg读取摄像头实现rtsp推流 拉流

windows下python opencv ffmpeg读取摄像头实现rtsp推流 拉流 整体流程1.下载所需文件1. 1下载rtsp推流服务器1.2 下载ffmpeg2. 开启RTSP服务器3. opencv 读取摄像头并调用ffmpeg进行推流4. opencv进行拉流整体流程 1.下载所需文件 1. 1下载rtsp推流服务器 下载 RTSP服务器 下…

jenkins 部署 vue 项目

jenkins 部署 vue 项目 环境 系统&#xff1a;CentOS7.9 Jenkins&#xff1a;最新LTS版本 nginx: 1.24.x gitLab: 打包机&#xff1a;jenkins所在服务器 目标机器&#xff1a;nginx所在服务器 jenkins部署配置 关键脚本 #node -v #已经安装node_module就无需执行install安…