[递归、搜索、回溯]----递归

 

前言

作者:小蜗牛向前冲

专栏小蜗牛算法之路

 专栏介绍"蜗牛之道,攀登大厂高峰,让我们携手学习算法。在这个专栏中,将涵盖动态规划、贪心算法、回溯等高阶技巧,不定期为你奉上基础数据结构的精彩算法之旅。一同努力,追逐技术的星辰大海。"

 

 

目录

一、递归

1、什么是递归

2、为什么会用到递归 

3、如何去理解递归 

4、如何求写递归

二、搜索 

1、深度和宽度

2、搜索

3、拓展搜索

三、回溯和剪枝  

四、刷题时刻 

1、汉诺塔问题

 a、算法原理

b、代码实现

2、合并两个有序链表(easy)

a、算法原理

b、代码实现

3、反转链表(easy)

a、算法原理

 b、代码实现

四、 两两交换链表中的节点(medium)

a、算法原理

b、代码实现

五、Pow(x, n)- 快速幂(medium)

a、算法原理

b、代码实现


这里是主讲算法刷题,一些概念可以看博主以前写的博客,下面会带链接。后面就不会在提醒了,大家开心刷题吧! 

一、递归

1、什么是递归

简单的来说就是函数自调用自己。

在C语言的学习我们就接触了,然后在数据结构中的二叉数,快排,归并都有其身影。

 不清楚看这里:

详解函数递归 

[数据结构]~二叉树

[数据结构]-玩转八大排序(二)&&冒泡排序&&快速排序

[数据结构]-玩转八大排序(三)&&归并排序&&非比较排序

2、为什么会用到递归 

本质是为由主问题拆分到子问题,再由相似的子问题拆分到相同的子问题。

二叉树的遍历

快排 

 归并排序

3、如何去理解递归 

初学可以画递归展开图

在求做二叉树的简单题目

最后宏观的看到递归过程:

  • 不在拘泥于递归展开图
  • 把递归想象为一个黑盒
  • 让黑盒求完成任务 

4、如何求写递归

  •  先找到相同的子问题---->想函数的头。
  • 只关心子问题是如何解决的---->写出函数主体
  • 注意递归函数的出口

二、搜索 

1、深度和宽度

深度优先遍历vs 深度优先搜索(dfs)

深度优先遍历

其中深度优先表示从根节点开始,沿着每个分支尽可能深入,直到达到树或图的最底部,然后回溯到上一层,继续遍历其他分支。在这个过程中,我们尽可能深入地探索一个分支,直到无法继续为止,然后回溯。

深度优先搜索(Depth-First Search,DFS):

用于描述在图或树等数据结构上进行搜索的算法。DFS 是一种算法,通过深度优先的方式遍历或搜索图或树。它通常与递归或使用栈的迭代方法结合使用。DFS 通常用于解决图的连通性问题、拓扑排序、路径查找等问题。

这二个概念其实在某种程度是是一样的:我们只要记住他就是一条路走到黑。
宽度优先遍历vs 宽度优先搜索(bfs)

宽度优先遍历(Breadth-First Traversal):

宽度优先遍历从根节点开始,逐层地访问节点,先访问距离根节点最近的节点,然后是相邻的、同一层级的节点,依此类推,直到遍历完整个数据结构。

宽度优先搜索(Breadth-First Search,BFS):

它从根节点开始,逐层地探索图中的节点,先探索距离起始节点最近的节点,然后是相邻的、同一层级的节点,依此类推,直到找到目标节点或者遍历完整个数据结构。

这二个概念其实在某种程度是是一样的:我们只要记住他就一层层走的。 

2、搜索

简单的来说就是暴力枚举一遍所以数据。

通过dfs或者bfs .

3、拓展搜索

其实搜索不仅仅局限鱼树或图等数据结构问题求解,只要一个问题的子问题可以全排列为,一课树状图的问题都可以用搜索解决。

比如对于1,2,3进行全排列

三、回溯和剪枝  

回溯本质就是深搜,剪枝的本质就是将回溯过后发现不对的部分去掉。

拿下面的走迷宫举例:

 =

首先我们从起点出发通过深度搜索来都到1节点,有二条路,走向红色那一条,碰壁后返回的1节点的过程就是回溯。在来看2节点, 发现二路都不对,回溯后将那二条路去掉的过程就是剪枝。

四、刷题时刻 

1、汉诺塔问题

在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。

你需要原地修改栈。

示例1:

 输入:A = [2, 1, 0], B = [], C = []
 输出:C = [2, 1, 0]

示例2:

 输入:A = [1, 0], B = [], C = []
 输出:C = [1, 0]

提示:

  1. A中盘子的数目不大于14个。
class Solution {
public:void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {}
};

 a、算法原理

拿到一个题目,我们完成对题意的理解后,首先会想:

怎么去解决这个问题?

一般情况:我们都是按照题意先试试着去模拟。 

当N==1时:我们直接把一个盘子从A移动C

当N==2时:我们要想把大盘上面的小盘,放在B,在把大盘放在C后,将小盘移动过来C。

当N==3时:我们把大盘上面的部分想办法移动到B,然后在把大便移动到C,最后想办法将小盘部分移动到C就可以了、

当N==n,时候,重复上图序号的过程就可以了

这种情况不就,我们将一个大问题,转换为一个子问题,子问题在转换为,同类型的子问题。所以这就切合递归。

递归解题思路:

1、重复问题---函数头

重复问题将x柱子上面的盘子,借助y柱子,移动到z柱子上

函数头: void dfs(x,y,z,n) 

2、只关系子问题在做什么

这里以N==3来切入思考:

 ​

dfs(x,z,y,n-1) --- B 将大盘上面的盘子移走(1)

x.back() ---C  将大盘子移动到z柱子上(2)

dfs(y ,x,z)--->C 将小盘部分移动到z柱子上(3)

这里自己可以简单画图理解!

3、递归的出口在哪里

N==1的时候,我们就不要在借助其他盘子了,直接移动到z柱子上就可以了。

x.back() ---z;

b、代码实现

class Solution {
public:void hanota(vector<int>& A, vector<int>& B, vector<int>& C){int n = A.size();dfs(A, B, C, n);}void dfs(vector<int>& x, vector<int>& y, vector<int>& z, int n){//递归出口if (n == 1){z.push_back(x.back());x.pop_back();return;}//函数体dfs(x, z, y, n - 1);z.push_back(x.back());x.pop_back();dfs(y, x, z, n - 1);}
};

LeetCode测试: 

2、合并两个有序链表(easy)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列1
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {}
};

a、算法原理

合并二个升序的链表,我们只要先从二个链表头结点选出,最小的那个结点拿出来,其余形成二个新的链表,我们让函数dfs帮我们合成一个链表,在链接最小的那个结点。这不就相同的子问题吗?

所以我们这里可以用递归解决:

函数头: 

    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) 

函数体:相同的子问题:

从二个链表头结点选出最小的那个list1(假设为最小)

list1->next = mergeTwoLists(list1->next, list2) ;

return list1;

递归结束:

那个链表为空就返回另外一个链表。

b、代码实现

class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2){if (list1 == nullptr)return list2;if (list2 == nullptr)return list1;if (list1->val < list2->val){list1->next = mergeTwoLists(list1->next, list2);return list1;}else{list2->next = mergeTwoLists(list1, list2->next);return list2;}}
};

LeetCode测试:  

 

3、反转链表(easy)

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* reverseList(ListNode* head) {}
};

a、算法原理

既然这道题目可以用递归解决问题,那么翻转整体链表,就可以分为一个子问题。

视角一:从宏观视角看 

我们要翻转链表,可以分为:

  • 让当前结点后面的结点链表进行逆置,返回头结点就好了
  • 让当前结点添加到后面逆置链表即可

 视角二:将链表看成一颗树

那不就只进行一次dfs遍历就好了(树的后序遍历) 

递归实现:

函数头

    ListNode* reverseList(ListNode* head) 

子问题

        ListNode* newhead =  reverseList(head->next);
        //将当前结点连接到逆置链表
        head->next->next = head;
        head->next = nullptr;

递归的出口在哪里

  当head==nullptr 或者head->next==nullptr;
      return head;

 b、代码实现

class Solution {
public:ListNode* reverseList(ListNode* head){//一个结点或者没有结点就不需要逆置//细节不要这样会报错if(head->next==nullptr||head==nullptr)//表达式是有顺序的这样会先判断:head->next==nullptr//但是要是head为nullptr就是空指针的引用了if (head == nullptr || head->next == nullptr)return head;//子问题//返回逆置当前结点后面链表,返回新头结点ListNode* newhead = reverseList(head->next);//将当前结点连接到逆置链表head->next->next = head;head->next = nullptr;//返回新的头结点return newhead;}
};

LeetCode测试: 

四、 两两交换链表中的节点(medium)

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

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

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100] 内
  • 0 <= Node.val <= 100
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* swapPairs(ListNode* head) {}
};

a、算法原理

对于链表类问题,大家一定要画图前理解

首先站宏观的视角看待,我们要将相邻二个结点交换,我们可以分为前二个结点,和后面一段,后面一段我们交给一个函数swapPairs我相信他一定能完成对里面结点进行交换,怎么完成的我们不关心。

然后在如图进行连接。

那递归结束的条件是什么:

当我们的结点为空或者只有一个节点就返回head. 

b、代码实现

class Solution {
public:ListNode* swapPairs(ListNode* head){if (head == nullptr || head->next == nullptr)return head;ListNode* tmp = swapPairs(head->next->next);ListNode* newhead = head->next;newhead->next = head;head->next = tmp;return newhead;}
};

 LeetCode测试: 

 

五、Pow(x, n)- 快速幂(medium)

 

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

class Solution {
public:double myPow(double x, int n) {}
};

a、算法原理

这道题目最容易想到是暴力,就是遍历相乘就好了,但是这样肯定是会超时的。

我们可以思考一下:当我们 x和n为上面二种情况的时候,我们通过不断划分子问题,从而求出结果,这不就是递归吗?

函数头:int pow(x,n)我们相信这函数可以帮助我们进行幂计算

函数体:只关系子问题做了什么 

tmp = pow(x,n/2);

return  n%2==0? tmp*tmp:tmp*tmp*x

递归出口:n==0时return 1; 

细节问题:

n可能出现负数:

也就是说可能出现3^(-2) 那我们的计算结果应该是1/(3^(2))

n可能为-2^{31}

如果我们把他转换为正数处理,int是存放不下的,所以我们要用long long去存(进行强转)

b、代码实现

class Solution {
public:double myPow(double x, int n){return n > 0 ? Pow(x, n) : 1.0 / Pow(x, -(long long)n);}double Pow(double x, long long n){if (n == 0)return 1.0;double tmp = Pow(x, n / 2);return n % 2 == 0 ? tmp * tmp : tmp * tmp * x;}
};

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

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

相关文章

C++面向对象程序设计-北京大学-郭炜【课程笔记(五)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;五&#xff09;】 1、常量对象、常量成员函数1.1、常量对象1.2、常量成员函数1.3、常引用 2、友元&#xff08;friends&#xff09;2.1、友元函数2.2、友元类 3、运算符重载的基本概念3.1、运算符重载 4、赋值运算符的重…

Guitar Pro 8.1中文版永久许可证激活2024最新24位注册激活码生成器

Guitar Pro是一款非常受欢迎的音乐制作软件&#xff0c;它可以帮助用户创建和编辑各种音乐曲谱。从其诞生以来就送专门为了编写吉他谱而研发迭代的。 尽管这款产品可能已经成为全球最受欢迎的吉他打谱软件&#xff0c;在编写吉他六线谱和乐队总谱中始终处于行业领先地位&#…

『运维备忘录』之 iptables 防火墙使用指南

前言 iptables 是一个配置 Linux 内核防火墙的命令行工具&#xff0c;它是用来设置、维护和检查Linux内核的IP包过滤规则的。本文将介绍 iptables 的基础知识和使用示例。 注意&#xff1a;红帽/红旗/CentOS等 7 版本以上已改为使用 firewalld 作为防火墙替换iptables。 一、基…

数据结构 - Trie树(字符串统计、最大异或对)

文章目录 前言Part 1&#xff1a;Trie字符串统计1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 Part 2&#xff1a;最大异或对1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 前言 本篇博客将介绍Trie树的常见应用&#xff0c;包括&#xff1a;Trie…

IEEE独立出版 | 院士出席,投递获取免费参会,与院士交流机会!

第五届信息科学与并行、分布式处理国际学术会议&#xff08;ISPDS 2024&#xff09;2024 5th International Conference on Information Science, Parallel and Distributed Systems2024年5月31-6月2日 | 中国广州 重要信息 大会官网&#xff1a;www.ispds.org 大会时间&…

Java后端八股笔记

Java后端八股笔记 Redis八股 上两种都有可能导致脏数据 所以使用两次删除缓存的技术&#xff0c;延时是因为数据库有主从问题需要更新&#xff0c;无法达到完全的强一致性&#xff0c;只能达到控制一致性。 一般放入缓存中的数据都是读多写少的数据 业务逻辑代码&#x1f44…

2024 AI 辅助研发的新纪年

随着人工智能技术的持续发展与突破&#xff0c;2024年AI辅助研发正成为科技界和工业界瞩目的焦点。从医药研发到汽车设计&#xff0c;从软件开发到材料科学&#xff0c;AI正逐渐渗透到研发的各个环节&#xff0c;变革着传统的研发模式。在这一背景下&#xff0c;AI辅助研发不仅…

python 截取字符串string.split

目录 作用语法只要第一个值获得第3个值遍历 作用 根据某个符号对数据进行截取 从而获得自己想要的内容 语法 使用’string.split’ 方法 对字符串’123/abc/BPYC’ 以 ‘/’ 进行截取 string "123/abc/BPYC" substring string.split("/") print(subs…

【Spring知识体系】1.1 Java 注解(Annotation)

文章目录 1.1 注解&#xff08;Annotation&#xff09;1.1.1 什么是注解1.1.2 内置注解1.1.3 元注解&#xff08;5种&#xff09;1.14 自定义注解1.15 注解使用场景介绍※ 本文小结 1.1 注解&#xff08;Annotation&#xff09; 1.1.1 什么是注解 注解的定义&#xff1a;它提…

目标检测——摩托车头盔检测数据集

一、简介 首先&#xff0c;摩托车作为一种交通工具&#xff0c;具有高速、开放和稳定性差的特点&#xff0c;其事故发生率高&#xff0c;伤亡率排在机动车辆损伤的首位。因此&#xff0c;摩托车乘员头盔对于保护驾乘人员头部安全至关重要。在驾乘突发状况、人体受冲击时&#…

Elasticsearch:向量相似度计算 - 可笑的速度

作者&#xff1a;Chris Hegarty 任何向量数据库的核心都是距离函数&#xff0c;它确定两个向量的接近程度。 这些距离函数在索引和搜索期间执行多次。 当合并段或在图表中导航最近邻居时&#xff0c;大部分执行时间都花在比较向量的相似性上。 对这些距离函数进行微观优化是值…

C语言从入门到精通 第十二章(程序的编译及链接)

写在前面&#xff1a; 本系列专栏主要介绍C语言的相关知识&#xff0c;思路以下面的参考链接教程为主&#xff0c;大部分笔记也出自该教程。除了参考下面的链接教程以外&#xff0c;笔者还参考了其它的一些C语言教材&#xff0c;笔者认为重要的部分大多都会用粗体标注&#xf…

Doris-数据分区

数据分区&#xff1a;即将大表划分为小表&#xff0c;数据分区主要有两个级别&#xff1a;Partition和Bucket&#xff08;Tablet&#xff09;。 Partition&#xff1a;逻辑分区&#xff0c;按照一定规则将表按照行进行划分&#xff0c;每个部分就是一个Partition。 Bucket&…

根据用户名称实现单点登录

一、参数格式 二、后端实现 Controller层 public class IAccessTokenLoginController extends BaseController {Autowiredprivate ISysUserService sysUserService;Autowiredprivate ISingleTokenServiceImpl tokenService;/*** 登录方法** return 结果*/PostMapping("/l…

AI智能分析网关V4智慧园区视频智能监管方案

一、背景需求分析 随着科技的不断发展&#xff0c;智慧园区建设已成为现代城市发展的重要方向。通过智能化技术提高园区的运营效率、降低成本、增强环境可持续性等具有重要作用。视频智能监管作为智慧园区安全管理体系的重要组成部分&#xff0c;对于提高园区的安全管理水平和…

手写简易操作系统(一)--环境配置

本专栏是我新开设的一个学术专栏&#xff0c;旨在全面介绍手写操作系统的相关内容。其中包括实模式向保护模式的过渡、锁机制、信号量操作、内存分配、硬盘驱动、文件系统、简单shell和管道等操作系统核心知识。该专栏旨在为有意开发自己操作系统的研究人员提供指导与帮助。作为…

阿里云服务器怎么使用?3分钟搭建网站教程2024新版

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网aliyunfuwuqi.com以搭建WordPress网站博客为例&#xff0c;来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流…

探索数据结构:单链表的实战指南

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty‘s blog 前言 在上一章节中我们讲解了数据结构中的顺序表&#xff0c;知道了顺序…

【校园导航小程序】2.0版本 静态/云开发项目 升级日志

演示视频 【校园导航小程序】2.0版本 静态/云开发项目 演示 首页 重做了首页&#xff0c;界面更加高效和美观 校园指南页 新增了 “校园指南” 功能&#xff0c;可以搜索和浏览校园生活指南 地图页 ①弃用路线规划插件&#xff0c;改用SDK开发包。可以无阻通过审核并发布…

吴恩达机器学习-可选实验室:特征工程和多项式回归(Feature Engineering and Polynomial Regression)

文章目录 目标工具特征工程和多项式回归概述多项式特征选择功能备用视图扩展功能复杂的功能 恭喜! 目标 在本实验中&#xff0c;你将:探索特征工程和多项式回归&#xff0c;它们允许您使用线性回归的机制来拟合非常复杂&#xff0c;甚至非常非线性的函数。 工具 您将利用在以…