22
33> ** 递归(Recursion)** :指的是一种通过重复将原问题分解为同类的子问题而解决的方法。在绝大数编程语言中,可以通过在函数中再次调用函数自身的方式来实现递归。
44
5- 举个例子来说明一下递归算法 。比如阶乘的计算方法在数学上的定义为:
5+ 举个简单的例子来了解一下递归算法 。比如阶乘的计算方法在数学上的定义为:
66
7- $fact(n) = \begin{cases} 1 & \text{if n = 0} \cr n * fact(n - 1) & \text{if n > 0} \end{cases}$
7+ $fact(n) = \begin{cases} 1 & \text{n = 0} \cr n * fact(n - 1) & \text{n > 0} \end{cases}$
88
99根据阶乘计算方法的数学定义,我们可以使用调用函数自身的方式来实现阶乘函数 ` fact(n) ` ,其实现代码可以写作:
1010
@@ -15,7 +15,7 @@ def fact(n):
1515 return n * fact(n - 1 )
1616```
1717
18- 以 ` n = 6 ` 为例,上述代码中阶乘函数 ` fact(6) ` 的计算过程如下:
18+ 以 ` n = 6 ` 为例,上述代码中阶乘函数 ` fact(6): ` 的计算过程如下:
1919
2020``` Python
2121fact(6 )
@@ -36,7 +36,7 @@ fact(6)
3636
3737上面的例子也可以用语言描述为:
3838
39- 1 . 函数从 ` fact(6) ` 开始,一层层地调用 ` fact(5) ` 、` fact(4) ` 、… 一直到最底层的 ` fact(0) ` 。
39+ 1 . 函数从 ` fact(6) ` 开始,一层层地调用 ` fact(5) ` 、` fact(4) ` 、…… 一直调用到最底层的 ` fact(0) ` 。
40402 . 当 ` n == 0 ` 时,` fact(0) ` 不再继续调用自身,而是直接向上一层返回结果 ` 1 ` 。
41413 . ` fact(1) ` 通过下一层 ` fact(0) ` 的计算结果得出 ` fact(1) = 1 * 1 = 1 ` ,从而向上一层返回结果 ` 1 ` 。
42424 . ` fact(2) ` 通过下一层 ` fact(1) ` 的计算结果得出 ` fact(2) = 2 * 1 = 2 ` ,从而向上一层返回结果 ` 2 ` 。
@@ -52,7 +52,7 @@ fact(6)
52521 . 先逐层向下调用自身,直到达到结束条件(即 ` n == 0 ` )。
53532 . 然后再向上逐层返回结果,直到返回原问题的解(即返回 ` fact(6) == 720 ` )。
5454
55- 这两个部分也可以叫做「递推过程」和「归回过程 」,如下面两幅图所示:
55+ 这两个部分也可以叫做「递推过程」和「回归过程 」,如下面两幅图所示:
5656
5757![ ] ( https://qcdn.itcharge.cn/images/20220407160648.png )
5858
@@ -71,18 +71,18 @@ fact(6)
7171
7272递归的数学模型其实就是「数学归纳法」。这里简单复习一下数学归纳法的证明步骤:
7373
74- 1 . 证明当 ` n = b ` ( ` b ` 为基本情况,通常为 ` 0 ` 或者 ` 1 ` )时,命题成立。
75- 2 . 证明当 ` n > b ` 时,假设 ` n = k ` 时命题成立,那么可以推导出 ` n = k + 1 ` 时命题成立。这一步不是直接证明的,而是先假设 ` n = k ` 时命题成立,利用这个条件,可以推论出 ` n = k + 1 ` 时命题成立。
74+ 1 . 证明当 $ n = b$ ($b$ 为基本情况,通常为 $0$ 或者 $1$ )时,命题成立。
75+ 2 . 证明当 $ n > b$ 时,假设 $ n = k$ 时命题成立,那么可以推导出 $ n = k + 1$ 时命题成立。这一步不是直接证明的,而是先假设 $ n = k$ 时命题成立,利用这个条件,可以推论出 $ n = k + 1$ 时命题成立。
7676
77- 通过以上两步证明,就可以说:当 ` n >= b ` 时,命题都成立。
77+ 通过以上两步证明,就可以说:当 $ n >= b$ 时,命题都成立。
7878
7979我们可以从「数学归纳法」的角度来解释递归:
8080
81- - ** 递归终止条件** :数学归纳法第一步中的 ` n = b ` ,可以直接得出结果。
82- - ** 递推过程** :数学归纳法第二步中的假设部分(假设 ` n = k ` 时命题成立),也就是假设我们当前已经知道了 ` n = k ` 时的计算结果。
83- - ** 回归过程** :数学归纳法第二步中的推论部分(根据 ` n = k ` 推论出 ` n = k + 1 ` ),也就是根据下一层的结果,计算出上一层的结果。
81+ - ** 递归终止条件** :数学归纳法第一步中的 $ n = b$ ,可以直接得出结果。
82+ - ** 递推过程** :数学归纳法第二步中的假设部分(假设 $ n = k$ 时命题成立),也就是假设我们当前已经知道了 $ n = k$ 时的计算结果。
83+ - ** 回归过程** :数学归纳法第二步中的推论部分(根据 $ n = k$ 推论出 $ n = k + 1$ ),也就是根据下一层的结果,计算出上一层的结果。
8484
85- 事实上,数学归纳法的思考过程也正是在解决某些数列问题时,可以使用递归算法的原因。比如阶乘、数组前 ` n ` 项和、斐波那契数列等等。
85+ 事实上,数学归纳法的思考过程也正是在解决某些数列问题时,可以使用递归算法的原因。比如阶乘、数组前 $n$ 项和、斐波那契数列等等。
8686
8787## 3. 递归三步走
8888
@@ -107,9 +107,9 @@ fact(6)
107107
108108那么我们应该如何思考「递推过程」和「回归过程」呢,又该如何写出递归中的递推公式呢?
109109
110- 如果一个问题 A ,可以分解为若干个规模较小、与原问题形式相同的子问题 B、C、D ,那么这些子问题就可以用相同的解题思路来解决。我们可以假设 B、C、D 已经解决了,然后只需要考虑在这个基础上去思考如何解决问题 A 即可。不需要再一层层往下思考子问题与子子问题、子子问题与子子子问题之间的关系。这样理解起来就简单多了。
110+ 如果一个问题 $A$ ,可以分解为若干个规模较小、与原问题形式相同的子问题 $B$、$C$、$D$ ,那么这些子问题就可以用相同的解题思路来解决。我们可以假设 $B$、$C$、$D$ 已经解决了,然后只需要考虑在这个基础上去思考如何解决问题 $A$ 即可。不需要再一层层往下思考子问题与子子问题、子子问题与子子子问题之间的关系。这样理解起来就简单多了。
111111
112- 从问题 A 到分解为子问题 B、C、D 的思考过程其实就是递归的「递推过程」。而从子问题 B、C、D 的解到问题 A 的解的思考过程其实就是递归的「回归过程」。想清楚了「如何划分子问题」和「如何通过子问题来解决原问题」这两个过程,也就想清楚了递归的「递推过程」和「回归过程」。
112+ 从问题 $A$ 到分解为子问题 $B$、$C$、$D$ 的思考过程其实就是递归的「递推过程」。而从子问题 $B$、$C$、$D$ 的解回到问题 $A$ 的解的思考过程其实就是递归的「回归过程」。想清楚了「如何划分子问题」和「如何通过子问题来解决原问题」这两个过程,也就想清楚了递归的「递推过程」和「回归过程」。
113113
114114然后,我们只需要考虑原问题与子问题之间的关系,就能够在此基础上,写出递推公式了。
115115
@@ -123,9 +123,9 @@ fact(6)
123123
124124在写出递推公式和明确终止条件之后,我们就可以将其翻译成代码了。这一步也可以分为 3 步来做:
125125
126- 1 . 定义递归函数:明确函数意义、传入参数、返回结果等。
127- 2 . 书写递归主体:提取重复的逻辑,缩小问题规模。
128- 3 . 明确递归终止条件:给出递归终止条件,以及递归终止时的处理方法。
126+ 1 . ** 定义递归函数** :明确函数意义、传入参数、返回结果等。
127+ 2 . ** 书写递归主体** :提取重复的逻辑,缩小问题规模。
128+ 3 . ** 明确递归终止条件** :给出递归终止条件,以及递归终止时的处理方法。
129129
130130#### 3.3.1 定义递归函数
131131
@@ -198,15 +198,27 @@ def recursion(大规模问题):
198198
199199** 示例** :
200200
201+ - 示例 1:
202+
201203``` Python
202- 输入 n = 2
203- 输出 1
204- 解释 f(2 ) = f(1 ) + f(0 ) = 1 + 0 = 1
204+ 输入:n = 2
205+ 输出:1
206+ 解释:F(2 ) = F(1 ) + F(0 ) = 1 + 0 = 1
207+ ```
208+
209+ - 示例 2:
210+
211+ ``` Python
212+ 输入:n = 3
213+ 输出:2
214+ 解释:F(3 ) = F(2 ) + F(1 ) = 1 + 1 = 2
205215```
206216
207217#### 5.1.3 解题思路
208218
209- 根据递归三步走策略,写出对应的递归代码。
219+ ##### 思路 1:递归算法
220+
221+ 根据我们的递推三步走策略,写出对应的递归代码。
210222
2112231 . 写出递推公式:` f(n) = f(n - 1) + f(n - 2) ` 。
2122242 . 明确终止条件:` f(0) = 0, f(1) = 1 ` 。
@@ -217,7 +229,7 @@ def recursion(大规模问题):
217229 1 . ` if n == 0: return 0 `
218230 2 . ` if n == 1: return 1 `
219231
220- #### 5.1.4 代码
232+ ##### 思路 1: 代码
221233
222234``` Python
223235class Solution :
@@ -229,6 +241,11 @@ class Solution:
229241 return self .fib(n - 1 ) + self .fib(n - 2 )
230242```
231243
244+ ##### 思路 1:复杂度分析
245+
246+ - ** 时间复杂度** :$O((\frac{1 + \sqrt{5}}{2})^n)$。具体证明方法参考 [ 递归求斐波那契数列的时间复杂度,不要被网上的答案误导了 - 知乎] ( https://zhuanlan.zhihu.com/p/256344121 ) 。
247+ - ** 空间复杂度** :$O(n)$。每次递归的空间复杂度是 $O(1)$, 调用栈的深度为 $n$,所以总的空间复杂度就是 $O(n)$。
248+
232249### 5.2 二叉树的最大深度
233250
234251#### 5.2.1 题目链接
@@ -243,25 +260,29 @@ class Solution:
243260
244261** 说明** :
245262
246- - 二叉树的深度:根节点到最远叶子节点的最长路径上的节点数。
247- - 叶子节点:没有子节点的节点。
263+ - ** 二叉树的深度** :根节点到最远叶子节点的最长路径上的节点数。
264+ - ** 叶子节点** :没有子节点的节点。
248265
249266** 示例** :
250267
268+ - 示例 1:
269+
251270``` Python
252- 输入 [3 ,9 ,20 ,null,null,15 ,7 ]
271+ 输入: [3 ,9 ,20 ,null,null,15 ,7 ]
253272对应二叉树
254273 3
255274 / \
256275 9 20
257276 / \
258277 15 7
259- 输出 3
260- 解释 该二叉树的最大深度为 3
278+ 输出: 3
279+ 解释: 该二叉树的最大深度为 3
261280```
262281
263282#### 5.2.3 解题思路
264283
284+ ##### 思路 1: 递归算法
285+
265286根据递归三步走策略,写出对应的递归代码。
266287
2672881 . 写出递推公式:` 当前二叉树的最大深度 = max(当前二叉树左子树的最大深度, 当前二叉树右子树的最大深度) + 1 ` 。
@@ -272,7 +293,7 @@ class Solution:
272293 2 . 书写递归主体:` return max(self.maxDepth(root.left) + self.maxDepth(root.right)) ` 。
273294 3 . 明确递归终止条件:` if not root: return 0 `
274295
275- #### 5.2.4 代码
296+ ##### 思路 1: 代码
276297
277298``` Python
278299class Solution :
@@ -283,6 +304,11 @@ class Solution:
283304 return max (self .maxDepth(root.left), self .maxDepth(root.right)) + 1
284305```
285306
307+ ##### 思路 1:复杂度分析
308+
309+ - ** 时间复杂度** :$O(n)$,其中 $n$ 是二叉树的节点数目。
310+ - ** 空间复杂度** :$O(n)$。递归函数需要用到栈空间,栈空间取决于递归深度,最坏情况下递归深度为 $n$,所以空间复杂度为 $O(n)$。
311+
286312## 参考资料
287313
288314- 【书籍】算法竞赛入门经典:训练指南 - 刘汝佳,陈锋 著
0 commit comments