自己调用自己的算法——递归算法

自己调用自己的算法——递归算法

目录

1. 什么是递归

2.具体例题讲解

2.1 LeetCode面试题 08.06. 汉诺塔问题

2.2 LeetCode21. 合并两个有序链表

2.3 LeetCode206. 反转链表

2.4 LeetCode50. Pow(x, n)

3. 总结


今天我们来聊一聊递归算法。

1. 什么是递归

相信各位在学习计算机的过程中都是听说过递归,它的本质就是通过自己调用自己的方式来实现一些问题。

它的代码一般来说是比较简单,那么我们该怎么设计递归呢,或者说什么样的问题是可以使用递归的呢?

在递归的设计中我们都需要以下这几步:

1. 确定问题是可以被分成多个重复子问题的。并且这些⼦问题具有与原问题相同的解决方法。

2. 把这些子问题的共同点确定出来,并从宏观的角度去看待这些问题。

3. 构建函数头,这个函数头表示的是什么,解决的是什么问题。

4. 接着去思考递归的出口。

PS:递归的出口就是递归的结束条件。一个递归是一定要有出口的,同时这个出口是要求一定会在递归的执行过程中触发的。

其实在我看来递归其实就是我们在算法的学习过程中发现一些问题可以被分为一个个小问题,接着这些小问题又可以被分为一些小问题。然后我们根据这个逻辑设计了一种代码或者说一种算法,这个算法和那些问题一样可以不断地被分化。我们管这些算法叫做递归。

说实话,我一开始学习的时候感觉这个算法总是有一种左脚踩右脚上天的感觉。

2.具体例题讲解

2.1 LeetCode面试题 08.06. 汉诺塔问题

我们来看下面这个问题,就是要求我们借助B这个柱子把A上面的盘子放到C上。

我们看下面这个图片,里面是1个盘子,2个盘子和3个盘子的情况。当只有一个盘子的时候我们只需要把A上面的这个盘子给放到C上。当是2个盘子的时候,我们先借助C把盘子给到B上,接着把A上面最大的那个盘子给C,接着用同样的方法把B盘子中最大的那个给C,循环往复。当是3个盘子时也是同样的方法。然后我们就发现了这个重复子问题,就是不断的通过借助中间柱子把盘子给放到C上面。

那我们的出口是什么呢?出口就是当A或者B上面只有一个盘子的时候我们就该结束了,直接把最后一个盘子给放到C上面,接着直接return。

我们看下面这个代码,dfs这个函数头表示X借助Y的帮助把盘子放到Z上。按照前面所说的,我们先借助Z把X上面的n-1个盘子给转移到B上,接着把X上面的那个盘子给放到Z上。接着借助X把Y上面的盘子给转移到Z上面。

PS:我们这边要注意,就是递归的出口基本上是写在最前面的,这样才可以防止死循环。

class Solution { public: 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); } void hanota(vector<int>& a, vector<int>& b, vector<int>& c) { dfs(a,b,c,a.size()); } };

2.2 LeetCode21. 合并两个有序链表

我们接下来看下面这道题,这道题的题目很简单,就是要求我们把下面两个链表给合并成一个链表。注意在这道题中我们不可以再生成一个链表。

这道题我们直接看代码,我们直接在题目给的函数里面进行递归,首先这个函数的函数头就是合并链表,出口就是但我们一个链表为空的时候直接返回另一个链表。

接着我们就开始判断两个链表里面的值,小的那个放到前面,接着再进行递归。因为我们前面说过了,这个函数头表示的是合并链表,我们我们直接当前节点的next=它就好了,代码会自己递归下去的。

class Solution { public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { if(!l1) return l2; if(!l2) return l1; if(l1->val<=l2->val) { l1->next=mergeTwoLists(l1->next,l2); return l1; } else { l2->next=mergeTwoLists(l1,l2->next); return l2; } } };

2.3 LeetCode206. 反转链表

我们看下面这道题,题目就是要求我们翻转链表。

前面忘记说了,就是一道题的话并不是唯一解法的,像这道题我们既可以用递归,也可以使用一个三指针。

我们看下面这个代码dfs这个函数头表示的是当前需要进行反转的节点,接着出口就是当我们遇到最后节点或者空节点的时候进行return。

其中因为是单链表,所以我们在这里需要提前保存最后一个节点的位置,也就是这里面的newnode。接着的话就是叫当前节点的后一个节点指向当前节点,然后当前节点指向空(在这里直接指向空不会干扰到前面的节点,因为递归的过程中间已经记录下了上一层节点了)。

/** * 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* dfs(ListNode* l1) { if(!l1||!l1->next) { return l1; } ListNode* newnode=dfs(l1->next); l1->next->next=l1; l1->next=nullptr; return newnode; } ListNode* reverseList(ListNode* head) { return dfs(head); } };

2.4 LeetCode50. Pow(x, n)

我们看下面这道题,就是叫我们手动实现一个pow函数。接着我们发现在这道题里面,n即可以能是一个负数,也可能是一个很大的数,所以我们在设计代码的时候要注意。

我们看下面这个图,下面这个就是我们发现重复子问题的过程,2的16次方可以通过两个2的8次方相乘来实现,2的8次方可以通过两个2的4次方相乘来实现。这样我们就发现了重复子问题。我们的递归也就按照它来进行设计。

我们看下面这个代码,首先名为要给m赋值给一个long long类型的值,因为m会很大。同时我们要注意这个m是可以为负数的,所以在这里如果为负数的话我们就要把x=1/x;接着把转换成正数(这一步我们是把负数的那个负号提前给x)。接着我们就按照上面所说的方式来实现递归,代码从下往上一点点走,下面那个ifelse判断就是判断有没有走到最后,也就是上面图片里面的1X2这一步。

class Solution { public: double myPow(double x, int m) { long long n=m; if(n<0) { x=1/x; n=-n; } if(n==0) return 1.0; double tmp=myPow(x,n/2); if(n%2==0) { return tmp*tmp; } else return tmp*tmp*x; } };

3. 总结

在递归算法的学习中我们一定要理解自己调用自己这个过程。同时也要理解递归是先走到最后面或者最下面再向上面返回的,它是有一个从小到大,从后到前的过程的。

如果还是觉得有点不理解,那么就画递归展开图。

Read more

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人 * 📋 文章目录结构 * 1.3 一键安装 OpenClaw(推荐) * 1.4 通过 npm 手动安装 * 1.5 运行 Onboard 向导 * 1.6 验证安装 * 步骤二:配置 Coding Plan 模型 * 🅰️ 选项 A:阿里百炼 Coding Plan * A.1 订阅与获取凭证 * A.2 在 OpenClaw 中配置 * A.3 可用模型列表

By Ne0inhk
龙虾机器人(OpenClaw)本地部署完全技术指南

龙虾机器人(OpenClaw)本地部署完全技术指南

龙虾机器人(OpenClaw)本地部署完全技术指南 前言:什么是“龙虾机器人”? 在开始部署之前,我们需要明确部署的对象。通常所说的“龙虾机器人”指的是开源项目 OpenClaw(曾用名:Clawdbot、Moltbot)。它由程序员彼得·斯坦伯格开发,是一个开源的、可本地部署的通用型AI代理系统。与ChatGPT等对话式AI不同,OpenClaw被赋予了操作系统的权限:它可以执行终端命令、读写文件、操控浏览器、安装软件,甚至通过MCP协议调用外部工具。 由于其强大的系统操控能力,安全性是部署时需关注的首要问题。官方及社区普遍建议:不要在主力机或存有敏感数据的生产环境直接裸奔部署,最好使用虚拟机、Docker容器或专用硬件(如Mac Mini或AI开发盒子)进行隔离。 第一章:环境准备与核心依赖 在安装OpenClaw之前,必须准备好运行环境。OpenClaw的核心由TypeScript编写,因此Node.js是必不可少的运行环境。此外,根据安装方式的不同,可能还需要Git、Docker或Python环境。 1.1 硬件建议与系统选择 * Linux

By Ne0inhk
MySQL 函数大赏:聚合、日期、字符串等函数剖析

MySQL 函数大赏:聚合、日期、字符串等函数剖析

MySQL系列 文章目录 * MySQL系列 * 前言 * 一、聚合函数 * 二、日期函数 * 三、字符串函数 * 四、数学函数 * 五、其他函数 前言 MySQL 提供了丰富的内置函数,用于处理数据、执行计算、转换格式等操作,本篇将介绍MySQL中常用的一些函数。 本篇文章内容已操作为主 这里的函数比较简单,不再解释了,再对其解释就有一种强说愁的感觉了 上篇文章:MySQL 数据操作全流程:创建、读取、更新与删除实战 一、聚合函数 这部分函数都比较简单 函数名作用示例结果SUM(col)求和SUM(amount)所有 amount 的总和AVG(col)平均值AVG(age)平均年龄COUNT(col)计数(忽略 NULL)COUNT(

By Ne0inhk
Linux to go Ubuntu 22.04 不匹配无线网卡 MT7925 的解决方法

Linux to go Ubuntu 22.04 不匹配无线网卡 MT7925 的解决方法

目录 * 一、手机 USB 共享网络 * 1. Windows 下 * 2. Linux 下 * 二、升级至 Ubuntu 24.04 * 1. 前提 * 1)备份数据 * 2)确保稳定的运行环境 * 3)检查当前系统状态 * 2. 升级系统 * 1)更新当前系统以及重启系统 * 2)检查 / 安装升级管理工具 * 3)修改并确认升级设置 * 4)开始升级 * 5)验证升级结果 * 6)升级后清理与优化 * 3. EFI系统分区(ESP)无法使用 * 1)检查现有的 ESP 分区 * 2)手动挂载 ESP

By Ne0inhk