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

 

前言

作者:小蜗牛向前冲

专栏小蜗牛算法之路

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

 

 

目录

一、递归

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;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

freeRTOS20240308

1.总结任务的调度算法&#xff0c;把实现代码再写一下 2.总结任务的状态以及是怎么样进行转换的

音视频学习笔记——c++多线程(一)

✊✊✊&#x1f308;大家好&#xff01;本篇文章主要整理了部分多线程相关的内容重点&#x1f607;。首先讲解了多进程和多线程并发的区别以及各自优缺点&#xff0c;之后讲解了Thead线程库的基本使用。 本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统…

react的diff源码

react 的 render 阶段&#xff0c;其中 begin 时会调用 reconcileChildren 函数&#xff0c; reconcileChildren 中做的事情就是 react 知名的 diff 过程 diff 算法介绍 react 的每次更新&#xff0c;都会将新的 ReactElement 内容与旧的 fiber 树作对比&#xff0c;比较出它们…

消息队列-kafka-消息发送流程(源码跟踪) 与消息可靠性

官方网址 源码&#xff1a;https://kafka.apache.org/downloads 快速开始&#xff1a;https://kafka.apache.org/documentation/#gettingStarted springcloud整合 发送消息流程 主线程&#xff1a;主线程只负责组织消息&#xff0c;如果是同步发送会阻塞&#xff0c;如果是异…

【CSP试题回顾】202104-2-邻域均值

CSP-202104-2-邻域均值 关键点&#xff1a;二维差分数组 详见&#xff1a;【CSP考点回顾】差分数组 解题思路 初始化矩阵和参数&#xff1a;首先&#xff0c;代码接收矩阵的大小&#xff08;n x n&#xff09;&#xff0c;每个元素的亮度值&#xff08;位于[0, L]区间&…

基于Vue的体育汇App设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 核心技术的理论与分析 3 1.1 客户端技术 3 1.1.1 Vue.js框架 3 1.1.2 Vue.js路由管理 3 1.1.3 Vuex状态管理 3 1.1.4 MVVM开发模式 4 1.1.5 Vant组件库 5 1.2 服务端技术 5 1.2.1 Node.js 5 1.2.2 Egg.js框架 5 1.3 数据库技术 6 1.4 本章…

webUI自动化测试框架

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

【LeetCode】升级打怪之路 Day 16:二叉树题型 —— 二叉树的构造

今日题目&#xff1a; 654. 最大二叉树105. 从前序与中序遍历序列构造二叉树106. 从中序与后序遍历序列构造二叉树889. 根据前序和后序遍历构造二叉树 目录 LC 654. 最大二叉树 【easy】 Problem&#xff1a;根据遍历序列来还原二叉树 【classic】 ⭐⭐⭐⭐⭐LC 105. 从前序与中…

数据库原理实验课(1)

目录 实验内容 安装头歌中的相关内容 具体过程 完结撒花~ 我也是第一次接触oracle的相关软件和操作&#xff0c;所以是一次傻瓜式教学记录 实验内容 安装头歌中的相关内容 具体过程 这是我在百度网盘中下载解压出来的oracle文件夹内的全部内容&#xff08;可能有因为安装完…

使用Portainer让测试环境搭建飞起来

Docker的用处不多加赘述&#xff0c;Docker目前有以下应用场景&#xff1a; 测试&#xff1a;Docker很适合用于测试发布&#xff0c;将 Docker 封装后可以直接提供给测试人员进行运行&#xff0c;不再需要测试人员与运维、开发进行配合&#xff0c;进行环境搭建与部署。 测试…

【技术】基于Github Pages搭建个人博客静态网页

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、技术基础二、新建特殊仓库三、上传网页文件四、Github Pages设置 个人网页作为仅服务个人的网页&#xff0c;一…

Grafana变量默认全选

注&#xff1a;本文基于Grafana v9.2.8编写 1 问题 EKS集群里的node按照不同label被分为几类&#xff0c;我需要对这几类的node做一些统计。我希望当我使用lable选择时&#xff0c;node的值自动设置为该lable的所有node集合&#xff0c;而不需要再手动全选。 2 解决方案 变…

微信小程序(五十四)腾讯位置服务示范(2024/3/8更新)

教程如下&#xff1a; 上一篇 1.先在官网注册一下账号&#xff08;该绑定的都绑定一下&#xff09; 腾讯位置服务官网 2.进入控制台 3.创建应用 3. 额度分配 4.下载微信小程序SDK 微信小程序SDK下载渠道 5.解压将俩js文件放在项目合适的地方 6.加入安全域名or设置不验证合…

扩展CArray类,增加Contain函数

CArray不包含查找类的函数&#xff0c;使用不便。考虑扩展CArray类&#xff0c;增加Contain函数&#xff0c;通过回调函数暴露数组元素的比较方法&#xff0c;由外部定义。该方法相对重载数组元素的“”符号更加灵活&#xff0c;可以根据需要配置不同的回调函数进行比较 //类型…

AD20中关于“failed to add class member”的解决方法

目录 问题描述&#xff1a; 解决方法&#xff1a; 1.切换至PCB界面-选择工具栏的设计-类 2.把Component classes中的所有的类全部按照图中删除&#xff0c;保存 3.重新返回原理图界面转换PCB即可成功 问题描述&#xff1a; failed to add class member&#xff1a;未能添加…

解答关于:水牛社软件是做什么的?水牛社软件靠谱么?

很多对我们软件感兴趣但是没有入手的观望者都会有这样的疑问&#xff1a;水牛社软件具体是做什么的&#xff1f;水牛社软件靠谱么&#xff1f; 其实软件的简介已经讲的很清楚了&#xff0c;但是软件不是功能性软件&#xff0c;所以不能给大家免费试用&#xff0c;短期任务版块…

智能驾驶规划控制理论学习08-自动驾驶控制模块(轨迹跟踪)

目录 一、基于几何的轨迹跟踪方法 1、基本思想 2、纯追踪 3、Stanly Method 二、PID控制器 三、LQR&#xff08;Linear Quadratic Regulator&#xff09; 1、基本思想 2、LQR解法 3、案例学习 基于LQR的路径跟踪 基于LQR的速度跟踪 4、MPC&#xff08;Mode…

Python通过SFTP实现网络设备配置备份

一、背景 为了防止网络设备意外损坏&#xff0c;导致配置文件无法恢复&#xff0c;可以通过将网络设备的配置文件备份到本地电脑上。 一般情况下&#xff0c;设备支持通过FTP、TFTP、FTPS、SFTP和SCP备份配置文件。其中使用FTP和TFTP备份配置文件比较简单&#xff0c;但是存在…

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

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

尚硅谷JavaScript高级学习笔记

01 准备 JavaScript中函数是对象。我们后续描述构造函数的内存模型时&#xff0c;会将构造函数称为构造函数对象。 02 数据类型 typeof 运算符来查看值的类型&#xff0c;它返回的是类型的字符串值 会做数据转换 03 相关问题 04数据_变量_内存 05相关问题1 06相关问题2 …