递归,一度让我头疼,这个算法有时候太绕了,为了彻底学习递归所以 我从头整理了一下,并举出几个例子,从简单到复杂的分析递归!
到底什么是递归,先看看360百科的定义:
递归是指函数/过程/子程序在运行过程序中直接或间接调用自身而产生的重入现象。
在计算机编程里,递归指的是一个过程:函数不断引用自身,直到引用的对象已知。
下面举一个最简单的递归例子:
LeetCode 面试题 08.05. 递归乘法 递归乘法。
写一个递归函数,不使用 * 运算符,
实现两个正整数的相乘。可以使用加号、减号、位移,但要吝啬一些。示例1:
输入:A = 1, B = 10 输出:10 示例2:
输入:A = 3, B = 4 输出:12
提示:保证乘法范围不会溢出
下面是python实现代码:
class Solution(object):
def multiply(self, A, B):
"""
:type A: int
:type B: int
:rtype: int
"""
if A>B:A, B = B, A
return B+self.multiply(A-1, B) if A>0 else 0
我们一起来分析一下代码过程:
假如:A=3,B=4
第一次引用自身:
4 + self.multiply(2, 4)
第二次引用自身:
4 + 4 + self.multiply(1, 4)
第三次引用自身:
4 + 4 + 4 + self.multiply(0, 4)
第四次引用自身:
4 + 4 + 4 + 0(由于A==0,所以最后+0)
循序渐进,看下一道稍难理解的递归用法:
LeetCode 剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如,给出 前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
class Solution(object):
def buildTree(self, preorder, inorder):
"""
:type preorder: List[int]
:type inorder: List[int]
:rtype: TreeNode
"""
def recur(pre_root, in_left, in_right):
if in_left > in_right:return
root = TreeNode(preorder[pre_root]) # 建立当前子树的根节点
i = inorder.index(preorder[pre_root])
root.left = recur(pre_root+1, in_left, i-1)
root.right = recur(pre_root+i-in_left+1, i+1, in_right)
return root
return recur(0, 0, len(inorder) - 1)
递推参数: 前序遍历中根节点的索引pre_root、中序遍历左边界in_left、中序遍历右边界in_right。
终止条件: 当 in_left > in_right ,子树中序遍历为空,说明已经越过叶子节点,此时返回 null。
递推工作:
建立根节点root: 值为前序遍历中索引为pre_root的节点值。
搜索根节点root在中序遍历的索引i
构建根节点root的左子树和右子树: 通过调用 recur() 方法开启下一层递归。左子树: 根节点索引为 pre_root + 1 ,中序遍历的左右边界分别为 in_left 和 i - 1。
右子树: 根节点索引为 pre_root + i - in_left+ 1(即:根节点索引 + 左子树长度 + 1),中序遍历的左右边界分别为 i + 1 和 in_right。
以前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7]为例,进行逐步分析:
- 第一次调用:
前序:
3 | 9 | 20 | 15 | 7 |
---|---|---|---|---|
pre_root |
中序:
9 | 3 | 15 | 20 | 7 |
---|---|---|---|---|
in_left | i | in_right |
- 第二次调用:
前序:
3 | 9 | 20 | 15 | 7 |
---|---|---|---|---|
pre_root(递归左子树) | pre_root (递归右子树) |
中序:
9 | 3 | 15 | 20 | 7 |
---|---|---|---|---|
in_left /in_right/ i | (以3为界,左边左子树,右边右子树) | in_left | i | in_right |
- 第三次调用:
中序中左子树 [9] 只有一个元素9,此时in_left = in_right = i
继续调用recur()函数,root.left = recur(1, 0, -1) 此时 in_left > in_right
中序中右子树 [ 15, 20 , 7]
继续调用recur()函数,root.left = recur(3, 2, 2) ,root.right = recur(4, 4, 4)
再看一道题:
- 二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
def diameterOfBinaryTree(self, root):
"""
:type root: TreeNode
:rtype: int
"""
self.nodes = 1
def dfs(node):
if not node:return 0
l = dfs(node.left)
r = dfs(node.right)
# 记录当前节点下最长节点数
self.nodes = max(self.nodes, l+r+1)
# 返回每个节点作为根节点的深度(从而改变l、r的值)
return max(l, r)+1
dfs(root)
return self.nodes-1
如果不分析可能会奇怪,代码为什么不是下面这样子的:
def diameterOfBinaryTree(self, root):
"""
:type root: TreeNode
:rtype: int
"""
self.nodes = 1
def dfs(node):
if not node:return 0
l = dfs(node.left)
r = dfs(node.right)
return max(self.nodes, l+r+1)
dfs(root)
return dfs(root)-1
大家可以看下区别,下面左图为正确代码的递归实现,右边为第二个代码错误的方法,return max(l, r)+1每一次都会返回当前节点的深度,从而改变self.nodes = max(self.nodes, l+r+1)中的 l、r 值,而return max(self.nodes, l+r+1)是返回当前节点为根节点下最多的节点数。
上一题作为过渡,再看一道难理解的题:
LeetCode 687. 最长同值路径
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。注意:两个节点之间的路径长度由它们之间的边数表示。
示例 1:
输入:
5
/ \
4 5
/ \ \
1 1 5
输出: 2
示例 2:
输入:
1
/ \
4 5
/ \ \
4 4 5
输出:: 2
注意: 给定的二叉树不超过10000个结点。 树的高度不超过1000。
讲真,这道题的递归,分析的我头疼
class Solution(object):
def longestUnivaluePath(self, root):
self.ans = 0
def arrow_length(node):
if not node: return 0
left_length = arrow_length(node.left)
right_length = arrow_length(node.right)
left_arrow = right_arrow = 0
if node.left and node.left.val == node.val:
left_arrow = left_length + 1
if node.right and node.right.val == node.val:
right_arrow = right_length + 1
# 记录以当前节点为根节点的树的最长同值路径
self.ans = max(self.ans, left_arrow + right_arrow)
# 记录当前节点下具有同值的最大深度
return max(left_arrow, right_arrow)
arrow_length(root)
return self.ans