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

【递归、搜索与回溯算法】递归算法

在这里插入图片描述

  • 递归算法简介
    • 1. 递归是什么
    • 2. 为什么会用到递归
    • 3. 如何理解递归
    • 4. 如何写好一个递归
  • 一、[汉诺塔问题](https://leetcode.cn/problems/hanota-lcci/description/)
  • 二、[合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/description/)
  • 三、[反转链表](https://leetcode.cn/problems/reverse-linked-list/description/)
  • 四、[两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/description/)
  • 五、[Pow(x, n)](https://leetcode.cn/problems/powx-n/description/)
  • 结尾

递归算法简介

1. 递归是什么

递归是一种通过函数自身调用自身来解决问题的编程思想,核心是将一个复杂问题分解为与原问题结构相似但规模更小的子问题,直到子问题小到可以直接解决,最终通过子问题的解组合得到原问题的解。

2. 为什么会用到递归

递归之所以被广泛使用,核心原因在于它能将复杂问题简化,尤其适合解决具有自相似结构或嵌套层次的问题。

3. 如何理解递归

  1. 把递归函数当成一个黑盒
  2. 相信黑盒一定能够完成这个任务

4. 如何写好一个递归

  1. 先找到相同的子问题 --> 函数头的设计
  2. 只关心某一个子问题是如何解决的 --> 函数体的书写
  3. 注意一下函数出口

一、汉诺塔问题

题目描述
在这里插入图片描述

思路讲解
本道题需要我们用栈将所有盘子从第一根柱子移到最后一根柱子,下面我就模拟一下该如何移动盘子才能完成目的。这里我设盘子的个数为N。

  • 当A上有一个盘子的时候,我们只需要将这一个盘子移动到C即可
  • 当A上有两个盘子的时候,我们需要先将这一最大的盘子移动到C
    1. 将一个(2-1)个盘子借助C移动到B上
    2. A上还剩一个最大的盘子,将它直接移动到C上
    3. 再将B上的一个(2-1)盘子借助A移动到C上,由于C上的是最大的盘子,可以叠加其他任意盘子,这一步就可以转化为当B上有一个盘子的时候,需要将这一个盘子移动到C
  • 当A上有三个盘子的时候,我们需要先将这一最大的盘子移动到C
    1. 将一个(3-1)个盘子借助C移动到B上
    2. A上还剩一个最大的盘子,将它直接移动到C上
    3. 再将B上的一个(3-1)盘子借助A移动到C上,由于C上的是最大的盘子,可以叠加其他任意盘子,这一步就可以转化为当B上有两个盘子的时候,需要将这些盘子移动到C
  • 当A上有四个盘子的时候,我们需要先将这一最大的盘子移动到C
    1. 将一个(4-1)个盘子借助C移动到B上
    2. A上还剩一个最大的盘子,将它直接移动到C上
    3. 再将B上的一个(4-1)盘子借助A移动到C上,由于C上的是最大的盘子,可以叠加其他任意盘子,这一步就可以转化为当B上有三个盘子的时候,需要将这些盘子移动到C
  • 当A上有n个盘子的时候,我们需要先将这一最大的盘子移动到C
    1. 将一个(n-1)个盘子借助C移动到B上
    2. A上还剩一个最大的盘子,将它直接移动到C上
    3. 再将B上的一个(n-1)盘子借助A移动到C上,由于C上的是最大的盘子,可以叠加其他任意盘子,这一步就可以转化为当B上有n-1个盘子的时候,需要将这些盘子移动到C

在这里插入图片描述

通过上面的模拟我们发现完成大问题时,会遇到相同的子问题,这就满足了使用递归的条件。以下是具体思路:

  • 终止条件
    • 当 n = 1 时(只有 1 个盘子),直接将盘子从 A 移到 C,无需递归
  • 问题分解
    • 要将 n 个盘子从柱子 A 移到柱子 C(借助柱子 B),可以拆分为 3 步:
      1. 将 n-1 个盘子从 A 移到 B(借助 C 作为过渡)
      2. 将第 n 个盘子(最大的盘子)从 A 直接移到 C
      3. 将 n-1 个盘子从 B 移到 C(借助 A 作为过渡)
  • 递归调用
    • 每次递归减少一个盘子,直到问题简化为移动单个盘子

编写代码

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

二、合并两个有序链表

题目描述
在这里插入图片描述

思路讲解
本道题需要我们合并两条有序的链表,通过对比两个指针指向的节点,取出较小的一个节点,将剩余节点与另一条链表进行合并,问题又变成了合并两条有序的链表,所以本道题可以使用递归的方式完成。
在这里插入图片描述

以下是具体思路:

  • 终止条件
    • 当其中一个链表为空时,直接返回另一个链表(因为剩余节点已有序)
  • 递归逻辑
    1. 比较两个链表的当前头节点值,选择值较小的节点作为合并后链表的当前节点
    2. 对选中节点的下一个节点与另一个链表的当前节点进行递归合并,结果作为选中节点的next
    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* list1, ListNode* list2) {if(!list1)  return list2;if(!list2)  return list1;ListNode* ret = nullptr;if(list1->val < list2->val){ret = list1;list1 = list1->next;}else{ret = list2;list2 = list2->next;}ret->next = mergeTwoLists(list1,list2);return ret;}
};

三、反转链表

题目描述
在这里插入图片描述

思路讲解
本道题需要我们反转整个链表,首先我们可以想想是否可以先将前两个节点翻转,但是这样会导致第三个及后序节点丢失。
在这里插入图片描述
先将前两个节点翻转会导致后序节点丢失,所以我们先让后面的链表进行翻转,重复这个过程,直到最后一个节点,再层层返回向前进行链接,即可完成对整体链表的翻转。
在这里插入图片描述

以下是具体思路:

  • 终止条件
    • 当链表为空或只有一个节点时,无需反转,直接返回当前节点
  • 递归逻辑
    • 找到链表的最后一个节点(新的头节点),这是递归的终止条件
    • 在回溯过程中,将当前节点的下一个节点的 next 指向当前节点,完成局部反转
    • 将当前节点的 next 设为 nullptr,避免形成环

编写代码

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

四、两两交换链表中的节点

题目描述
在这里插入图片描述

思路讲解
本道题需要我们两两交换链表的节点,我们先让后面的链表进行两两交换,重复这个过程,直到最后一个节点或空节点,再层层返回,将该层的两个节点进行交换,然后向前进行链接,即可完成两两交换链表的节点。
在这里插入图片描述
以下是具体思路:

  • 终止条件
    • 当链表中剩余节点数不足 2 个时,无需交换,直接返回当前节点
  • 递归处理后续节点
    • 先递归交换 head.next.next 及之后的所有节点,得到交换后的子链表头节点 newhead。这一步确保后续节点已经完成交换,为当前层的交换做好准备
  • 交换当前层的两个节点:
    • 记当前的两个节点为 first = head(第一个节点)和 second = head.next(第二个节点)
    • 交换操作:second 的 next 指向 first(完成两个节点的互换)
    • 连接后续链表:first 的 next 指向递归返回的 newHead(将当前交换后的节点与后续已交换的链表连接)
  • 返回新头节点:
    • 交换后,second 成为当前子链表的新头节点,返回 second 供上层递归使用

编写代码

/*** 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) {if(head == nullptr || head->next == nullptr)return head;ListNode* newhead = swapPairs(head->next->next);ListNode* ret = head->next;head->next->next = head;head->next = newhead;return ret;}
};

五、Pow(x, n)

题目描述
在这里插入图片描述

思路讲解
本道题想让我们计算 x 的整数 n 次幂函数,我们可以利用指数的二分特性:xnx^nxn = xn2x^{\frac{n}{2}}x2n *xn2x^{\frac{n}{2}}x2n(当 n 为偶数时),或 xnx^nxn = xn−12x^{\frac{n-1}{2}}x2n1 * xn−12x^{\frac{n-1}{2}}x2n1 * xxx(当 n 为奇数时)。通过递归将问题规模减半,避免逐次相乘的 O (n) 复杂度。以下是具体思路:

  • 特殊条件
    • 当 n = 0 时,任何数的 0 次幂为 1
    • 当 n < 0 时,xnx^nxn = 1/x−n1 / x^{-n}1/xn,先计算正指数幂再取倒数
  • 递归逻辑
    • 递归计算xn2x^{\frac{n}{2}}x2n 的结果(记为 tmp)
    • 根据 n 的奇偶性,返回 tmp* tmp(偶数)或 tmp* tmp* x(奇数)

编写代码

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

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹
在这里插入图片描述

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

相关文章:

  • 【代码随想录day 16】 力扣 513.找树左下角的值
  • 米哈游笔试——求强势顶点的个数
  • [python] typing 中的overload
  • Android视图回调机制:从post到ViewTreeObserver,从源码分析到最佳实践
  • MariaDB 数据库管理
  • 基于PyTorch一文讲清楚损失函数与激活函数并配上详细的图文讲解
  • Pytorch深度学习框架实战教程12:Pytorch混合精度推理,性能加速147%的技术实现
  • MPLS对LSP连通性的检测
  • 使用Blender可视化多传感器坐标系转换
  • 移动端常见的8大css兼容性问题和处理方法
  • #Linux内存管理# 浅析缺页中断中私有映射且发生写时复制COW的工作原理
  • 《Qt————Tcp通讯》
  • 容器网络隔离测试于VPS服务器环境的桥接模式验证
  • Docker 详细介绍及使用方法
  • 【大智慧数据】心智开花的时候
  • 数字图像处理3
  • 三、k8s 1.29 之 安装1网络 / ikuai路由器虚拟机安装
  • 【嵌入式】Linux的常用操作命令(2)
  • 防御保护10
  • AI优化SEO关键词实战应用
  • Java数据结构——LinkedList
  • scanpy单细胞转录组python教程(一):不同形式数据读取
  • Python爬虫实战:研究BlackWidow,构建最新科技资讯采集系统
  • Shell脚本-条件判断语法格式
  • React Profiler
  • 【代码随想录day 15】 力扣 404. 左叶子之和
  • 【无标题】命名管道(Named Pipe)是一种在操作系统中用于**进程间通信(IPC)** 的机制
  • Ubuntu 安装 Elasticsearch
  • Ubuntu 安装 Kibana
  • WGS84 与 ITRF 坐标系的差异及转换算法详解