递归究竟是什么?如何快速编写正确的递归代码? —— 力扣经典面试题详解

递归究竟是什么?如何快速编写正确的递归代码? —— 力扣经典面试题详解

  • 一、递归
    • 1.1 什么是递归?
    • 1.2 为什么会用到递归?
    • 1.3 如何快速编写正确的递归代码?
  • 二、力扣相关笔试题解析
    • [面试题 08.06. 汉诺塔问题](https://leetcode.cn/problems/hanota-lcci/description/)
    • [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/description/)
    • [LCR 024. 反转链表](https://leetcode.cn/problems/UHnkqh/description/)
    • [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/description/)

一、递归

1.1 什么是递归?

下面是来自百度百科对递归算法的定义:

 递归是一种算法设计技术,它允许一个函数在其定义或说明中有直接或间接调用自身的方法。
 递归在数学和计算机科学中有着广泛的应用,它通过将复杂问题分解为规模较小、形式相同的子问题来求解。递归的基本原理包括:每一级的函数调用都有自己的变量;每一次函数调用都会有一次返回;递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序;递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反;虽然每一级递归都有自己的变量,但是函数代码并不会得到复制。

简单来说,递归的本质就是函数自己调用自己!

1.2 为什么会用到递归?

 在回答这个问题前,我们先来看看二叉树的先序遍历是如何实现的?

 所谓先序遍历即依次遍历二叉树的根节点、左子树、右子树。在遍历过程中,我们发现左子树和右子树的遍历同样可以采用先序遍历来实现。即将先序遍历整颗二叉树转化先遍历完根节点后,在先序遍历来遍历左子树、右子树。而在先序遍历左/右子树时,我们可以将左子树和右子树当成一颗完整的二叉树,重复上述的过程,直到为空才结束。

  在求解问题时,我们发现可以将主问题可以分解为若干个相同的子问题,而这些子问题同样都可以分解为若干个相同的子子问题,不断重复。换句话说,假设我们可以通过一个方法f(可以将f想象成数学中求解问题的函数表达式f(x))来求解主问题,而主问题所转化出的子问题同样可以通过方法f来解决(而子问题所衍生出的子子问题同样可以通过方法f来解决,不断重复下去),从而实现函数自己调用自己!当面临这种情况时,即可使用递归算法来解决问题。

1.3 如何快速编写正确的递归代码?

  1. 找到相同的子问题,该过程决定了函数头的设计(即函数参数需要传哪些)
  2. 将其中一个子问题进行分析,在此过程中将递归函数当作一个黑盒,该函数能完成我们所赋予的功能。该过程决定了函数的函数体。
  3. 最后当然是返回值了。在何种情况下,递归结束。

下面以二叉树的先序遍历为例进行分析:
  首先先序遍历过程:根、左子树、右子树。而左子树和右子树显然是一个相同的子问题(左子树和右子树还可以继续细分下去,都行相同子问题,就不多说了)。所有我们需要给函数传一个根节点参数,用于分割整颗二叉树

void TreePrev(TreeNode* root)//函数头

  现在我们赋予了该递归函数功能:先序遍历二叉树。现在我们取其中一个子问题进行分析,首先我们需要遍历根节点,然后可以通过该递归函数来实现左子树、右子树的先序遍历了,即:

cout << root -> val <<" ";//根节点
//我们赋予了函数TreePrev先序遍历二叉树的功能,所有我们可以把该函数当作一个黑盒。
//只要我们将二叉树的根节点传入该黑盒,便可实现这颗二叉树的先序遍历。(实际该过程可以由画递归展开图验证,由于篇幅问题博主就不多说了)
TreePrev(root -> left);//遍历左子树
TreePrev(root -> right);//遍历右子树

  最后就是递归什么时候结束了。显然当根节点未空指针时,递归结束。此时返回空即可

if(root == nullptr)return;

所以整体代码就是:

void TreePrev(TreeNode* root)//函数头
{if(root == nullptr)return;cout << root -> val <<" ";//根节点//我们赋予了函数TreePrev先序遍历二叉树的功能,所有我们可以把该函数当作一个黑盒。//只要我们将二叉树的根节点传入该黑盒,便可实现这颗二叉树的先序遍历。(实际该过程可以由画递归展开图验证,由于篇幅问题博主就不多说了)TreePrev(root -> left);//遍历左子树TreePrev(root -> right);//遍历右子树	
}

二、力扣相关笔试题解析

面试题 08.06. 汉诺塔问题

在这里插入图片描述
【分析】:
 题目指让A柱子中的N个圆盘(自下而上降序)借助B转移到C柱子上,并且保存顺序不变。所以我们可以考虑将A柱子上的后N-1个圆盘当作一个整体移到B上,然后将A中剩余的最后一个圆盘移到C上;最后在将B盘上的所有圆盘当作一个整体移到C上即可达成目的!
 在题目的分析过程中,出现将A上的N-1个圆盘移到B上,将B上的N-1个圆盘移到C上的过程。而这两个步骤显然是一个递归子问题(将一个柱子上的N个圆盘,借助另一个柱子转移到第三个柱子)。由于该过程中是具体圆盘在3根柱子间的移到,所以函数头因有4个参数:柱A、柱B、柱C、移到圆盘个数!!

 由于该过程中,我们通过递归函数(黑盒)将A柱上的N-1个圆盘移到B柱上、将A柱上的剩余的最后一个圆盘移到C柱、通过递归函数(黑盒)将B中的所有圆盘移到C柱。所以函数体为:

//_hanota为我们待实现的递归函数,该函数的功能是将将一个柱子上的N个圆盘,借助另一个柱子转移到第三个柱子
//所以将该函数想成一个黑盒,只需向该函数传递正确参数,即可实现对应的功能!(具体由函数展开图证明)
//先将A中前num-1个圆盘借助C移到B
_hanota(A, C, B, num - 1);
//将A剩余的最后一个元素移到C柱子
C.push_back(A.back());
A.pop_back();
//将B上的num-1个圆盘借助A移到C柱子上
_hanota(B, A, C, num - 1);

 最后返回值就不多说了,A中只有一个圆盘时,无需解决B,直接将圆盘从A移到C上。

【下面是其中一个子问题的圆盘转移过程图解】:
在这里插入图片描述
【代码编写】:

class Solution {
public:void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {_hanota(A, B, C, A.size());}void _hanota(vector<int>& A, vector<int>& B, vector<int>& C, int num) {if(num == 1){C.push_back(A.back());A.pop_back();return;}//先将A中前num-1个圆盘借助C移到B_hanota(A, C, B, num - 1);//将A剩余的最后一个元素移到C柱子C.push_back(A.back());A.pop_back();//将B上的num-1个圆盘借助A移到C柱子上_hanota(B, A, C, num - 1);}
};

21. 合并两个有序链表

【题目】:
在这里插入图片描述
【解析】:
 这道题目的就是将两个升序的链表合并成一个升序链表,并返回新链表的头节点。
 在本题中,我们可以先选出两链表中节点值较小的节点,然后想办法将该链表中剩下的节点和另一条链表合并为升序链表;最后将最开始选出的较小节点的next指向该新合并的新节点即可解决问题。显然将该链表中剩下的节点和另一条链表合并为升序链表就是一个递归子问题,所以可以采用递归方式解决问题。
 由于递归子问题是合并两个有序链表,所有函数头传的参数为待合并的两个链表头节点。至于函数体,我们只需挑选出原来两链表中头节点值较小的节点作为新链表的头节点,然后通过递归函数将剩下的元素和另一个链表合并,最后将新链表的头节点的next指向合并链表的头节点。当链表节点为空时,递归结束。

【代码解析】:

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

LCR 024. 反转链表

在这里插入图片描述
反转链表我们可以将后n-1个节点反转(并返回反转后链表的头节点),然后在将原链表的头结合和新链表链接即可。
在这里插入图片描述

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

24. 两两交换链表中的节点

在这里插入图片描述
这道题本质上和反转链表类似,我们可以先将原链表头两个节点反转,然后利用递归子问题将后续所有节点当成一个完成链表,完成两两交换链表中的节点。最后将交换后的链表和原链表的头两个数据链接即可。(如果链表为空,或只有一个元素,则递归结束)

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地运行github上下载的项目--接Git入门篇

1.了解项目 这是一个基于Spring Boot 和 Mybatis Plus 构建的Java项目&#xff0c;很经典的外卖项目&#xff0c;参考b站的黑马瑞吉外卖。 2.构建项目 SpringBoot项目&#xff0c;首先下载一些常见的项目要求的组件。然后配置如下&#xff1a; 看README&#xff0c;在阅读该…

mongodb sharding分片模式的集群数据库,日志治理缺失导致写入数据库报错MongoWriteConcernException的问题总结(下)

一、接着上文 上文介绍了mongodb sharding的分片集群搭建&#xff0c;本文侧重于讲述日志治理。 这里使用linux自带的日志治理工具logrotate&#xff0c;无论是哪个端口的进程&#xff0c;其日志治理方式类似。 查看/data目录下的文件大小&#xff0c; du -hs *二、Logrota…

从零开始学大模型 | 你必须要知道的三种大模型架构可视化的方法!

引言 大模型架构可视化对于理解、解释和优化这些复杂模型具有重要意义和作用&#xff0c;主要包括以下两个方面&#xff1a; 提高模型透明度和可解释性通过可视化&#xff0c;我们能够直观地观察到模型内部的计算过程、参数分布、特征提取等&#xff0c;从而更好地理解模型是如…

前端学习记录——关于代码规范和代码格式化

代码规范&#xff1a;&#x1f449;详情 代码格式化&#xff1a;&#x1f449;详情 如何配置eslint&#xff1a; eslint配置文件&#xff1a;.eslintrc、.eslint.json 。定义代码风格规则和错误检查规则。eslint插件&#xff1a;应用eslint规则&#xff0c;实时检测代码规范…

【漏洞复现】通天星CMSV6弱口令漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

Linux_应用篇(02) 文件 I/O 基础

本章给大家介绍 Linux 应用编程中最基础的知识&#xff0c;即文件 I/O&#xff08;Input、 Outout&#xff09; &#xff0c; 文件 I/O 指的是对文件的输入/输出操作&#xff0c;说白了就是对文件的读写操作&#xff1b; Linux 下一切皆文件&#xff0c;文件作为 Linux 系统设计…

【C++】list介绍

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. list介绍2. list的构造3. ist iterator的使用4. capacity5. element access6. modifiers7. 迭代器失效8. Operations8.1 reverse8.2 sort8.3 unique8.4 splice 1. list介绍 list是可以在常数范围内在任意位置进行插…

解决pandas的concat表格错位问题。表格拼接错误。

两个表格横向拼接但没拼到一块儿 如图&#xff1a; 图片来源&#xff1a;https://m.163.com/dy/article/HM6T6DRQ0516W3V7.html 拼接错位了。 解决方法&#xff1a;重置左边表格索引。 import pandas as pd df1df1.reset_index(dropTrue) df_newpd.concat([df1,df2],axiis1)…

【算法-PID】

算法-PID ■ PID■ 闭环原理■ PID 控制流程■ PID 比例环节&#xff08;Proportion&#xff09;■ PID 积分环节&#xff08;Integral&#xff09;■ PID 微分环节&#xff08;Differential&#xff09; ■ 位置式PID&#xff0c;增量式PID介绍■ 位置式 PID 公式■ 增量式 PI…

机器学习-生存分析:基于QHScrnomo模型的乳腺癌患者风险评估与个性化预测

一、引言 乳腺癌作为女性常见的恶性肿瘤之一&#xff0c;对女性健康构成威胁。随着医疗技术的不断进步&#xff0c;个性化医疗逐渐成为乳腺癌治疗的重要方向。通过深入研究乳腺癌患者的风险评估和个性化预测&#xff0c;可以帮助医生更准确地制定治疗方案&#xff0c;提高治疗效…

Web框架开发-Form组件和ajax实现注册

一、注册相关的知识点 1、Form组件 我们一般写Form的时候都是把它写在views视图里面,那么他和我们的视图函数也不影响,我们可以吧它单另拿出来,在应用下面建一个forms.py的文件来存放 2、局部钩子函数 1 2 3 4 5 6 7 # 局部钩子函数 def clean_username(self): userna…

【C++】stack、queue和优先级队列

一、前言 二、stack类 2.1 了解stack 2.2 使用stack &#xff08;1&#xff09;empty &#xff08;2&#xff09;size &#xff08;3&#xff09;top &#xff08;4&#xff09;push &#xff08;5&#xff09;pop 2.3 stack的模拟实现 三、queue类 3.1 了解queue …