递归究竟是什么?如何快速编写正确的递归代码? —— 力扣经典面试题详解
- 一、递归
- 二、力扣相关笔试题解析
-
- [面试题 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 如何快速编写正确的递归代码?
- 找到相同的子问题,该过程决定了函数头的设计(即函数参数需要传哪些)
- 将其中一个子问题进行分析,在此过程中将递归函数当作一个黑盒,该函数能完成我们所赋予的功能。该过程决定了函数的函数体。
- 最后当然是返回值了。在何种情况下,递归结束。
下面以二叉树的先序遍历为例进行分析:
首先先序遍历过程:根、左子树、右子树。而左子树和右子树显然是一个相同的子问题(左子树和右子树还可以继续细分下去,都行相同子问题,就不多说了)。所有我们需要给函数传一个根节点参数,用于分割整颗二叉树。
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

最低0.47元/天 解锁文章
1441





