当前位置: 首页 > news >正文

算法 - 递归

递归

递归适用于那些可以“自然地分解为结构相似的更小子问题”的问题。

下面用一段话详细说明其题目类型和适用场景:

递归算法的题目类型主要集中在以下几类:

  1. 树形结构问题:如树的遍历(前序、中序、后序)、计算树高、判断对称性等,因为树本身就是用递归定义的(每个子树也是一棵树)。

  2. 分治算法:如归并排序、快速排序、二分查找,其核心思想就是将大问题分解为相互独立的子问题,分别解决后再合并结果。

  3. 回溯算法:如八皇后、全排列、组合总和等,在尝试所有可能的分步时,每一步都可以看作一个递归调用,失败后“回溯”到上一步。

  4. 动态规划:虽然常用迭代优化,但其核心思想源于递归——将问题分解为重叠子问题,比如斐波那契数列、爬楼梯问题。

  5. 递归定义的数据结构:如链表、JSON/XML解析,链表可以看作“一个节点+指向另一个链表的指针”,这种自相似性非常适合递归。

判断一个题目是否适用于递归,主要看它是否满足三个条件

  1. 问题可分解:原问题能够被分解成一个或几个规模更小、但结构与原问题完全相同的子问题。

  2. 子问题可解:这些子问题可以用同样的递归方法来解决。

  3. 有终止条件:存在一个简单情况(递归基),能直接得到答案而无需再递归。

题目练习

面试题 08.06. 汉诺塔问题 - 力扣(LeetCode)

解法(递归):

算法思路:

这是一道递归方法的经典题目,我们可以先从最简单的情况考虑:

  • 假设 n = 1,只有一个盘子,很简单,直接把它从 A 中拿出来,移到 C 上;

  • 如果 n = 2 呢?这时候我们就要借助 B 了,因为小盘子必须时刻都在大盘子上面,共需要 3 步(为了方便叙述,记 A 中的盘子从上到下为 1 号,2 号):

    • a. 1 号盘子放到 B 上;

    • b. 2 号盘子放到 C 上;

    • c. 1 号盘子放到 C 上。至此,C 中的盘子从上到下为 1 号,2 号。

  • 如果 n > 2 呢?这是我们需要用到 n = 2\ 时的策略,将 A 上面的两个盘子挪到 B 上,再将最大的盘子挪到 C 上,最后将 B 上的小盘子挪到 C 上就完成了所有步骤。

因为 A 中最后处理的是最大的盘子,所以在移动过程中不存在大盘子在小盘子上面的情况。

  1. 对于规模为 n 的问题,我们需要将 A 柱上的 n 个盘子移动到 C 柱上。

  2. 规模为 n 的问题可以被拆分为规模为 n - 1 的子问题:

    • a. 将 A 柱上的上面 n - 1 个盘子移动到 B 柱上。

    • b. 将 A 柱上的最大盘子移动到 C 柱上,然后将 B 柱上的 n - 1 个盘子移动到 C 柱上。

  3. 当问题的规模变为 n = 1 时,即只有一个盘子时,我们可以直接将其从 A 柱移动到 C 柱。

需要注意的是,步骤 2.b 考虑的是总体问题中的子问题 b 情况。在处理子问题的子问题 b 时,我们应该将 A 柱中的最上面的盘子移动到 C 柱,然后再将 B 柱上的盘子移动到 C 柱。在处理总体问题的子问题 b 时,A 柱中的最大盘子仍然是最上面的盘子,因此这种做法是通用的。

算法流程:

递归函数设计void hanota(vector<int>& A, vector<int>& B, vector<int>& C, int n)

  1. 返回值:无;

  2. 参数:三个柱子上的盘子,当前需要处理的盘子个数(当前问题规模);

  3. 函数作用:将 A 中的上面 n 个盘子挪到 C 中。

递归函数流程

  1. 当前问题规模为 n = 1 时,直接将 A 中的最上面盘子挪到 C 中并返回;

  2. 递归将 A 中最上面的 n - 1 个盘子挪到 B 中;

  3. 将 A 中最上面的一个盘子挪到 C 中;
  4. 将 B 中上面 n - 1 个盘子挪到 C 中。

class Solution {
public:void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {dfs(A, B, C, A.size());return;}void dfs(vector<int>& a, vector<int>& b, vector<int>& c, int n){if(n == 1) {c.push_back(a.back());a.pop_back();return;}dfs(a, c, b, n - 1);c.push_back(a.back());a.pop_back();dfs(b, a, c, n - 1);}
};

21. 合并两个有序链表 - 力扣(LeetCode)

解法(递归):

算法思路:

  1. 递归函数的含义:交给你两个链表的头结点,帮你把它们合并起来,并且返回合并后的头结点;
  2. 函数体:选择两个头结点中较小的结点作为最终合并后的头结点,然后将剩下的链表交给递归函数去处理;
  3. 递归出口:当某一个链表为空的时候,返回另外一个链表。

注意注意注意:链表的题一定要画图,搞清楚指针的操作!

/*** 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* l1, ListNode* l2) {if(l1 == nullptr) return l2;if(l2 == nullptr) return l1;if(l1->val <= l2->val){l1->next = mergeTwoLists(l1->next, l2);return l1;}else{l2->next = mergeTwoLists(l1, l2->next);return l2;}}
};

206. 反转链表 - 力扣(LeetCode)

解法(递归):

算法思路:

  1. 递归函数的含义:交给你一个链表的头指针,帮你逆序之后,返回逆序后的头结点;
  2. 函数体:先把当前结点之后的链表逆序,逆序完之后,把当前结点添加到逆序后的链表后面即可;
  3. 递归出口:当前结点为空或者当前只有一个结点的时候,不用逆序,直接返回。

注意注意注意:链表的题一定要画图,搞清楚指针的操作!

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

注意:

在递归反转链表的解法中,newhead 的核心作用就是记录反转后新链表的头节点(也就是原链表的尾节点)。

具体来说:

  1. 当递归触达原链表的最后一个节点时(即 head->next == nullptr),这个节点就是反转后新链表的头节点,此时返回该节点作为 newhead 的初始值。
  2. 在递归回溯的过程中,newhead 会被逐层向上传递(始终保持是新链表的头节点),不会被修改。
  3. 真正的反转操作是通过 head->next->next = head 完成的(让当前节点的下一个节点指向自己),而 newhead 仅仅是作为 “结果标识” 向上传递,直到最终返回整个反转链表的头节点。

24. 两两交换链表中的节点 - 力扣(LeetCode)

解法(递归):

算法思路:

  1. 递归函数的含义:交给你一个链表,将这个链表两两交换一下,然后返回交换后的头结点;

  2. 函数体:先去处理一下第二个结点往后的链表,然后再把当前的两个结点交换一下,连接上后面处理后的链表;

  3. 递归出口:当前结点为空或者当前只有一个结点的时候,不用交换,直接返回。

注意注意注意:链表的题一定要画图,搞清楚指针的操作!

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

50. Pow(x, n) - 力扣(LeetCode)

解法(递归 - 快速幂):

算法思路:

  1. 递归函数的含义:求出 x 的 n 次方是多少,然后返回;
  2. 函数体:先求出 x 的 n / 2 次方是多少,然后根据 n 的奇偶,得出 x 的 n 次方是多少;
  3. 递归出口:当 n 为 0 的时候,返回 1 即可。

#include<iostream>
#include<vector>using namespace std;class Solution {
public:double QuickPwd(double x, int n){if (n == 0){return 1.0;}double tmp = QuickPwd(x, n / 2);return n % 2 ? tmp * tmp * x : tmp * tmp;}double myPow(double x, int n) {long long N=n;return N > 0 ? QuickPwd(x, N) : 1 / QuickPwd(x, -N);}
};

http://www.dtcms.com/a/469748.html

相关文章:

  • 软考-系统架构设计师 系统架构评估详细讲解
  • Redis 黑马skyout
  • 【Unity】构建超实用的有限状态机管理类
  • redis基础命令和深入理解底层
  • Java中第三方报告库-Allure
  • 高端公司网站建设连云港做网站制作
  • Google 智能体设计模式:优先级排序
  • 网站做不做百度云加速手游代理平台哪个好
  • 【国内电子数据取证厂商龙信科技】邮件如何取证?
  • 手机网站模板 psd做网站建设分哪些类型
  • 做网站需要哪些框架网站没备案可以访问吗
  • Git下载和安装教程(附安装包)
  • go的学习2---》并发编程
  • 高端网站建设企业公司网页版qq空间登录入口官网
  • 麒麟系统安装达梦数据库遇到的问题
  • VScode怎么使用Jupyter并且设置内核
  • LwIP UDP RAW
  • VI-SLAM定位方案对比
  • TCP/IP 协议族—理论与实践(一)
  • 手持小风扇MCU方案,智能风扇方案设计开发
  • 网站设计深圳网站建设公司网页设计与制作100例怎么写
  • Linux -- 网络层
  • 建设班级网站 沟通无限网络黄页进入有限公司
  • Labview项目01:标准可配置序列测试框架
  • 拌合楼软件开发(23)监测客户端在线情况并联动企业微信提醒客户端离线和恢复
  • 雄安网建 网站建设莞城微信网站建设
  • 基于Python的交通数据分析应用-hadoop+django
  • [特殊字符] 教程|打造一个 Telegram 币圈波场交易记录检测机器人
  • 高弹性不锈钢材质在技工钳应用中的优势分析
  • 东土科技连投三家核心企业 发力具身机器人领域