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

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

目录

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从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk