代码随想录算法训练营三期 day 24 - 回溯 (1) (补)

news2024/11/16 5:48:23

回溯算法理论基础

什么是回溯法

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯。所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数。

回溯法的效率

回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,所以效率并不高。

回溯法解决的问题

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

如何理解回溯法

回溯法解决的问题都可以抽象为树形结构,集合的大小构成了树的宽度,递归的深度构成了树的深度。递归会有终止条件,所以一定是一棵高度有限的 N 叉树

回溯法模板

回溯三步曲
① 回溯函数参数及返回值:
先写逻辑,需要什么参数再填什么参数。一般没有返回值。

void backtracking(参数)

② 回溯函数终止条件:
一般来说搜到叶子结点了也就找到了满足条件的一条答案, 把这条答案存起来,并结束本层递归。

if (终止条件) {
    存放结果;
    return;
}

③ 回溯函数的遍历过程
回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成了树的深度。
请添加图片描述

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

for 循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个 for 循环就执行多少。backtracking 自己调用自己,实现递归。
从图中看出 for 循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
④ 分析完过程,回溯算法模板框架如下:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

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

组合问题

题目链接:77. 组合
原文链接:77. 组合
视频链接:77. 组合
树形结构:
请添加图片描述
回溯三部曲:
① 确定递归函数参数及返回值
两个全局变量:

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

参数除了 n n n k k k, 还需要有一个 s t a r t I n d e x startIndex startIndex,这个参数用来记录本层递归中集合从哪里开始遍历:

vector<int> path; 			// 用来存放符合条件结果
vector<vector<int>> res;   // 存放符合条件结果的集合
void backtracking(int n, int k, int startIndex) 

② 确定终止条件
path 这个数组的大小如果达到 k k k,说明我们找到了一个子集大小为 k k k 的组合了,在图中 path 存的就是根节点到叶子节点的路径。

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

③ 确定单层递归逻辑
回溯法的搜索过程就是一个树型结构的遍历过程,在下图中,可以看出 for 循环用来横向遍历,递归的过程是纵向遍历。
请添加图片描述
for 循环每次从 s t a r t I n d e x startIndex startIndex 开始遍历,然后用 p a t h path path 保存取到的节点 i i i
backtracking(递归函数)通过不断调用自己一直往深处遍历,总会遇到叶子节点,遇到了叶子节点就要返回。注意下一层搜索要从 i + 1 i + 1 i+1 开始。
backtracking 的下面部分就是回溯的操作了,撤销本次处理的结果。

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

代码如下:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            res.push_back(path);
            return;
        }
        for (int i = startIndex; i <= n; i++) {
            path.push_back(i);
            backtracking(n, k, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return res;
    }
};

组合问题的剪枝优化

视频链接:组合问题的剪枝操作
请添加图片描述
举一个例子, n = 4 n = 4 n=4 k = 4 k = 4 k=4 的话,那么第一层 f o r for for 循环的时候,从元素 2 2 2 开始的遍历都没有意义了。 在第二层 f o r for for 循环,从元素 3 3 3 开始的遍历都没有意义了。
图中每一个节点(图中为矩形),就代表本层的一个 f o r for for 循环,那么每一层的 f o r for for 循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
所以,可以剪枝的地方就在递归中每一层的 f o r for for 循环所选择的起始位置。
如果 f o r for for 循环选择的起始位置之后的元素个数已经不足我们需要的元素个数了,那么就没有必要搜索了。
优化过程如下:
(1) 已经选择的元素个数:path.size();
(2) 还需要的元素个数为: k - path.size();
(3) 列表中剩余元素(n - i) >= 还需要的元素个数(k - path.size());
(4) 在集合 n 中至多要从该起始位置 : i <= n - (k - path.size()) + 1; 开始遍历;
所以优化之后的 for 循环是:

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

优化后整体代码:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            res.push_back(path);
            return ;
        }
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
            path.push_back(i);
            backtracking(n, k, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return res;
    }
};

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

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

相关文章

【手把手教你学51单片机】中断的优先级

注&#xff1a;本文章转载自《手把手教你学习51单片机》&#xff01;因转载需要原文链接&#xff0c;故无法选择转载&#xff01; 如若侵权&#xff0c;请联系我进行删除&#xff01;上传至网络博客目的为了记录自己学习的过程的同时&#xff0c;同时能够帮助其他一同学习的小伙…

第四十三章 动态规划——最长单调序列模型

第四十三章 动态规划——最长单调序列模型一、最长单调序列模型1、模型母题2、思路分析&#xff08;两种方法&#xff1a;DP&#xff0c;贪心&#xff09;二、模型的应用1、AcWing 1017. 怪盗基德的滑翔翼&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析&#xff…

C规范编辑笔记(十四)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) C规则编辑笔记(十) C规范编辑笔记(十一) C规范编辑笔记(十二) C规范编辑笔记(…

Linux进程学习【一】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Perseverance is not a long race; it is many short races one after another…

Linux基本功系列之rename命令实战

文章目录一. rename 命令介绍二. 语法格式及常用选项三. 参考案例3.1 将当前目录下所有.cfg的文件&#xff0c;替换为.txt结尾3.2 将所有出现mufeng的部分都替换为mufeng13.3 将mufeng0开头都变成mufeng00开头3.4 rename支持正则表示式总结前言&#x1f680;&#x1f680;&…

2023-1-22 刷题情况

积水面积 先祝大家新年快乐&#xff0c;新的一年&#xff0c;万事如意。 题目描述 一组正整数&#xff0c;分别表示由正方体叠起的柱子的高度。若某高度值为 xxx&#xff0c;表示由 xxx 个正立方的方块叠起&#xff08;如下图&#xff0c;0≤x≤50000 \le x \le 50000≤x≤5…

卷积神经网络进阶--基础知识

卷积神经网络进阶 b站课程链接碳基生物都能学会的神经网络&#xff08;跳着看的&#xff09; 因为我用的是pytorch&#xff0c;而该课程是用tenserflow的&#xff0c;所以主要记了一下理论 为什么要讲不同的网络结构 不同的网络结构解决的问题不同不同的网络结构使用的技巧不同…

【人工智能原理自学】卷积神经网络:打破图像识别的瓶颈

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文讲解卷积神经网络&#xff1a;打破图像识别的瓶颈&#xff0c;一起卷起来叭&#xff01; 目录一、手写体识别二、“炼丹”一、手写体识别 在机器学习、神经网络领域&#…

【数据分析】(task4)数据可视化

note matplotlib的四个容器&#xff1a; Figure&#xff1a;顶层级&#xff0c;用来容纳子 Axes&#xff0c;一组具体绘图元素和画布&#xff08;canvas&#xff09;。 画板。Axes&#xff1a;matplotlib宇宙的核心&#xff0c;容纳了大量元素用来构造一幅幅子图&#xff0c;一…

【QT5.9】与MFC对比学习笔记-感悟篇【2023.01.22】

简介 在公司从事MFC的程序维护一年两个月&#xff0c;期间因为公司被QT告侵权对QT产生了抵触的心情。现在无奈要用到&#xff0c;需要抓紧学习了。 正文 1.数据模型 先说下刚用到的模型&#xff0c;模型也叫数据模型&#xff0c;也就是耳熟的MVC架构中的M&#xff08;Model…

我用笨办法啃下了一个开源项目的源码!

目录 1、从最简单的源码开始&#xff1a;别幻想一步登天 2、循序渐进&#xff1a;先搞定底层依赖的技术 3、一定要以Hello World作为入口来阅读 4、抓大放小&#xff0c;边写注释边画图 5、反复三遍&#xff0c;真正理解源码 6、借力打力&#xff0c;参考源码分析书籍及博客 7…

研一寒假C++复习笔记--引用的使用

​​​​​​​ 目录 1--引用的基本语法 2--引用的注意事项 3--在函数参数中使用引用 4--引用作函数的返回值 5--引用的本质 6--常量引用 1--引用的基本语法 引用相当于给变量起别名&#xff0c;其基本语法如下&#xff1a; 数据类型 &别名 原名 # include <…

Linux操作系统之进程信号

代码存放在&#xff1a;https://github.com/sjmshsh/System-Call-Learn/tree/master/signal 我们先来看一张图&#xff0c;了解一下通过阅读本博客&#xff0c;你可以收获什么。 背景知识 首先我说明一点 信号 ! 信号量 我们这篇文章讲解的是信号&#xff0c;不是信号量 信…

POJ3263. Tallest Cow题解(c++ 前缀和)

POJ3263. Tallest Cow 传送门&#xff1a;Tallest Cow 题目&#xff1a; 有N头牛站成一行。两头作能够相支看见&#xff0c;当且仅当它们中间的牛身高都比它们矮。现在&#xff0c;我们只知道其中最高的牛是第P头&#xff0c;它的身高是H&#xff0c;不知道剩余N-1头牛的身高。…

大数据之Kafka高级知识点

文章目录前言一、分片和副本机制&#xff08;一&#xff09;分片机制&#xff08;二&#xff09;副本二、Kafka如何保证数据不丢失&#xff08;一&#xff09;Producer生产者&#xff08;二&#xff09;Broker&#xff08;三&#xff09;Consumer消费者三、消息存储和查询机制总…

重新设计 TCP 协议

看一段关于 TCP 协议的历史讨论&#xff0c;源自&#xff1a;The design philosophy of the DARPA internet protocols 读这段文字时&#xff0c;你可能觉得这不是在谈 TCP&#xff0c;而是在创造一个新协议&#xff0c;但事实上这就是 TCP 在被创造过程中真实的纠结。 现在来…

Java知识点细节简易汇总——(6)面向对象编程(中级部分)

一、IDE快捷键 删除当前行, 默认是 ctrl Y 自己配置 ctrl d复制当前行, 自己配置 ctrl alt 向下光标补全代码 alt /添加注释和取消注释 ctrl / 【第一次是添加注释&#xff0c;第二次是取消注释】导入该行需要的类 先配置 auto import , 然后使用 altenter 即可快速格式化…

Day867.事务隔离 -MySQL实战

事务隔离 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于事务隔离的内容。 提到事务&#xff0c;肯定不陌生&#xff0c;和数据库打交道的时候&#xff0c;总是会用到事务。 最经典的例子就是转账&#xff0c;你要给朋友小王转 100 块钱&#xff0c;而此时你的银行…

[前端笔记——CSS] 10.层叠与继承、选择器

[前端笔记——CSS] 10.层叠与继承、选择器1.层叠与继承1.1 冲突规则1.2 继承1.3 层叠1.4 CSS位置的影响2.选择器2.1 选择器是什么&#xff1f;2.2 选择器列表2.3 选择器的种类类型、类和 ID 选择器标签属性选择器伪类与伪元素运算符选择器参考表1.层叠与继承 1.1 冲突规则 CS…

一起自学SLAM算法:8.1 Gmapping算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 下面将从原理分析、源码解读和安装与运行这3个方面展开讲解Gmapping 算法。 8.1.1 Gmapping原理分析 首先要知道&#xff0c;Gmapping是一种基于粒子滤波的算法。在7.7.2节中已经提到过用RBPF&#xff08;Rao-…