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

 

前言

作者:小蜗牛向前冲

专栏小蜗牛算法之路

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

 

 

目录

一、递归

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.mzph.cn/news/730444.shtml

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

相关文章

【操作系统概念】 第9章:虚拟内存管理

文章目录 0.前言9.1 背景9.2 按需调页9.2.1 基本概念9.2.2 按需调页的性能 9.3 写时复制9.4 页面置换9.4.1 基本页置换9.4.2 FIFO页置换9.4.3 最优(Optimal)置换9.4.4 LRU&#xff08;Least Recently Used&#xff09;页置换9.4.5 近似LRU页置换9.4.6 页缓冲算法 9.5 帧分配9.5…

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

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

3.8 动态规划 背包问题

一.01背包 46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; (kamacoder.com) 代码随想录 (programmercarl.com) 携带研究材料: 时间限制&#xff1a;5.000S 空间限制&#xff1a;128MB 题目描述: 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会…

2001-2022年上市公司利润表数据

2001-2022年上市公司利润表数据 1、时间&#xff1a;2001.12.31-2022.12.31 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、统计截止日期、报表类型、投资收益、其中&#xff1a;对联营企业和合营企业的投资收益、公允价值变动收益、营业利润、其他综…

探讨2024年AI辅助研发的趋势

一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经成为当今时代最具变革性的技术之一。AI的广泛应用正在重塑各行各业&#xff0c;其中&#xff0c;AI辅助研发作为科技和工业领域的一大创新热点&#xff0c;正引领着研发模式的深刻变革。从医药…

电脑小问题:Windows更新后黑屏

Windows 更新后黑屏解决方法 在 Windows 更新后&#xff0c;伴随了一个小问题&#xff0c;电脑启动后出现了桌面黑屏。原因可能是火绒把 explorer.exe 当病毒处理了。 下面讲解 Windows 更新后黑屏的解决方法&#xff0c;步骤如下&#xff1a; 1. 按 ctrl alt delete 组合键…

C语言从入门到精通 第十一章(文件操作)

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

升级Xcode 15-全程绿灯+高速公路

还没升级Xcode15的兄弟们&#xff0c;恭喜你们&#xff0c;看完这篇文章&#xff0c;让你的升级Xcode15 之路畅通无阻外加全程高速&#x1f60e;。 前提 系统要求&#xff1a;MacOS 13.5 或更高版本 系统没达到要求的兄弟&#xff0c;先下载好系统升级包&#xff0c;下载好了…

设计模式——2_4 中介者(Mediator)

我寄愁心与明月&#xff0c;随风直到夜郎西 ——李白《闻王昌龄左迁龙标遥有此寄》 文章目录 定义图纸一个例子&#xff1a;怎么调度一组地铁站台和地铁开车指挥中心 碎碎念中介者和表单平台思想但是这种平台便利性是要付出代价的变化隔离原则 姑妄言之 定义 用一个中介者对象…

抖店月销过万的爆单技巧,新手轻松月入1w+,附抖店学习资料!

我是电商珠珠 抖店开通之后&#xff0c;怎么才能快速出单是很多新手小伙伴困扰的问题。其实想要运营好抖店一点都不难&#xff0c;我做抖店也有三年多时间了&#xff0c;接下来我说的每一步&#xff0c;不管是有货源还是无货源的都适用。 1、铺货低价福利款 店铺开好之后&am…

ABAP 因去重和汇总导致金额数值错误

在去重之前&#xff0c;就有两条重复的&#xff0c;一旦进行分组汇总&#xff0c;金额就会翻倍&#xff0c;之后又进行去重&#xff0c;也是没有用的&#xff0c;错误数据是2588.6&#xff0c;是1294.3的两倍&#xff0c;现在试试先去重&#xff0c;再去计算数据 就是因为去重…

JAVA虚拟机实战篇之内存调优[4](内存溢出问题案例)

文章目录 版权声明修复问题内存溢出问题分类 分页查询文章接口的内存溢出问题背景解决思路问题根源解决思路 Mybatis导致的内存溢出问题背景问题根源解决思路 导出大文件内存溢出问题背景问题根源解决思路 ThreadLocal占用大量内存问题背景问题根源解决思路 文章内容审核接口的…

IP定位技术在金融风控中的应用研究

随着金融科技的快速发展&#xff0c;金融行业的风险也呈现出多样化、复杂化的特点。金融风控作为保障金融安全的重要手段&#xff0c;其面临的挑战也日益加剧。在这样的背景下&#xff0c;IP定位技术作为一种先进的信息技术手段&#xff0c;正逐渐成为金融风控领域的重要工具。…

会声会影软件界面字体太小 会声会影字体放大教程

会声会影做为一款经典且流行的剪辑软件&#xff0c;凭借其较低的入门门槛&#xff0c;直观的操作和丰富的功能&#xff0c;一直以来被很多用户所喜爱&#xff0c;这其中有部分是老年用户&#xff0c;他们可能因为视力较差&#xff0c;无法看清会声会影软件界面的文字的难题。今…

Dockerfile的使用,怎样制作镜像

Docker 提供了一种更便捷的方式&#xff0c;叫作 Dockerfile docker build命令用于根据给定的Dockerfile构建Docker镜像。 docker build命令参数&#xff1a; --build-arg&#xff0c;设置构建时的变量 --no-cache&#xff0c;默认false。设置该选项&#xff0c;将不使用Build …

C++指针(四)万字图文详解!

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09; 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…

本鲸:打造科技招商新引擎、实现政企资源高效对接

在当今这个快速变化的时代&#xff0c;科技创新已成为推动社会进步和经济发展的核心动力。本鲸&#xff0c;作为科技创新创业服务的平台&#xff0c;正以其独特的视角和专业服务&#xff0c;为政府和企业提供一站式科技招商解决方案&#xff0c;助力构建创新驱动的经济发展新模…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:ImageAnimator)

提供帧动画组件来实现逐帧播放图片的能力&#xff0c;可以配置需要播放的图片列表&#xff0c;每张图片可以配置时长。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 ImageAni…

MySql 组合索引的使用

MySql 组合索引的使用 测试Mysql组合索引在不同的查询条件组合下的索引使用情况。当有abc 3个字的的组合索引时&#xff0c;按照MySql 的左匹配原则&#xff0c;abc&#xff0c;ab&#xff0c;a 是满足左匹配原则&#xff0c;肯定是会走索引的&#xff0c;但是其他的场景&…

ubuntu 命令行配置WiFi 密码

文章目录 一、命令行设置WiFi密码的意义二、Ubuntu的命令行设置WiFi密码步骤 一、命令行设置WiFi密码的意义 提供了一种不依赖图形界面的方式来配置WiFi连接。这对于那些没有图形界面或者需要远程配置WiFi的情况非常有用。通过命令行设置WiFi密码&#xff0c;可以方便地在终端中…