递归算法:recursive algorithm (从小白到掌握)

        递归,一度让我头疼,这个算法有时候太绕了,为了彻底学习递归所以 我从头整理了一下,并举出几个例子,从简单到复杂的分析递归!

到底什么是递归,先看看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]为例,进行逐步分析:

  • 第一次调用:
    前序:
3920157
pre_root

中序:

9315207
in_leftiin_right
  • 第二次调用:
    前序:
3920157
pre_root(递归左子树)pre_root (递归右子树)

中序:

9315207
in_left /in_right/ i(以3为界,左边左子树,右边右子树)in_leftiin_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. 二叉树的直径
            给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :
        给定二叉树

      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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔的菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值