LeetCode 剑指Offer 数据结构之 递归 总结 Part1

LeetCode 剑指Offer 数据结构之 递归 总结 Part1

递归算法主要应用于树结构的搜索与遍历。其他方面也有一些经典的问题,比如斐波那契数列,跳台阶等,很多问题中动态规划都能替代递归算法。本章将对剑指Offer题库中几道递归算法考题进行总结,记录一些对于递归算法的入门理解,同时简单的与动态规划解法进行一些对比。关于动态规划的部分将在新的文章中进行总结。

递归算法核心在于函数中重复调用本函数,明确终止条件+下一次的函数调用输入(递推公式产生)即可

剑指 Offer 07:

重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

经典的递归问题,二叉树中有提到过。
先序遍历每科子树的第一位都是根节点,不断从中序遍历中找根节点的索引,对左右子树分别进行递归
终止条件是子树的先序遍历节点都放入root就停止

def buildTree(self, preorder, inorder):
   if not preorder or not inorder:
       return None
   root = TreeNode(preorder.pop(0))
   indexroot = inorder.index(root.val)
   root.left = self.buildTree(preorder, inorder[:indexroot])
   root.right = self.buildTree(preorder, inorder[indexroot+1:])
   return root

总结:生成树是自上而下的

剑指 Offer 10- I:

斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1

经典的递归,动态规划题
可以得出递归与动态规划的一些对比,以及带备忘录的递归优化方式:

解法1:

def fib(self, n: int) -> int:
   # method1 recursive
   if n == 0:
       return 0
   if n == 1:
       return 1
   return self.fib(n-1) + self.fib(n-2)

数列第n项,经典的递归公式:

a n + 2 = a n + 1 + a n a_{n+2} = a_{n+1} + a_{n} an+2=an+1+an
终止条件,两个递归函数n减少到0,或者1

解法2: 带dp空间的动态规划

def fib(self, n: int) -> int:
   # method1 recursive
   if n == 0:
       return 0
   if n == 1:
       return 1
   return self.fib(n-1) + self.fib(n-2)

解法3:带备忘录的递归
把已经得到的中间项用字典保存起来,在下次需要计算的时候先查找一下
主要是n-2,n-1不重复计算了

my_dic = {0:0, 1:1}
def my_fib(n):
    if n in my_dic:
        return my_dic[n]
    if n-2 not in my_dic:
        my_dic[n-2] = my_fib(n-2)
    if n-1 not in my_dic:
        my_dic[n-1] = my_fib(n-1)
    my_dic[n] = (my_dic[n-2] + my_dic[n-1])%1000000007
    return my_dic[n]
return my_fib(n)
剑指 Offer 10- II:

青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

考虑到最后一次跳跃,可以跳2,可以跳1所以所有的方式是以n-2最后一次跳2 + n-1最后一次跳1组成

递推公式:
a n = a n − 1 + a n − 2 a_{n} = a_{n-1} + a_{n-2} an=an1+an2
本题同样可以用动态规划 与 递归分别求解

def numWays(self, n):
	'''
	递归
	'''
    if n==0:
        return 1
    if n==1:
        return 1
    if n==2:
        return 2
    records = [-1 for i in range(n+1)]
    if records[n] == -1: # 表明这个值没有算过
        records[n] = (self.numWays(n-1) +self.numWays(n-2))%1000000007
    return records[n]
    
def numWays(self,n):
	'''
	动态规划
	'''
    a, b = 1, 1
    for _ in range(n):
        a, b = b, a + b# a代表dp[n],b代表dp[n-1],一次转移 b代表dp[n] ,a+b代表dp[n-1],dp[n-2]
    return a % 1000000007

显然动态规划空间复杂度很低

剑指 Offer 16:

数值的整数次方

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入: 2.00000, 10
输出: 1024.00000
示例 2:

输入: 2.10000, 3
输出: 9.26100
示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

快速幂,优化的根据是当幂为偶数时,可以先对底数平方,幂除以2节约计算
终止条件是幂不断除2取整之后等于0停止

def myPow1(self, x, n):
    if n == 0:# 终止条件
        return 1
    if n < 0:
        return self.myPow1(1 / x, -1 * n)
    if n % 2 == 0:
        return self.myPow1(x * x, n // 2)
    else:
        return x * self.myPow1(x * x, n // 2)

其他解法,二进制法(详细解释将在位运算总结中再进行归纳)

def myPow(self, x, n):
   if x == 0:
       return 0
   res = 1
   if n < 0:
       x, n = 1 / x, -n
   while n:
       if n & 1:
           res *= x
       x *= x
       n >>= 1
   return res

总结:递归主要依赖于递推公式+终止条件,往往是从下到上(n项从大到小,最后终止)的感觉。 有明确递推公式的问题都可以用动态规划进行替代,而且动态规划还可以通过优化dp空间,状态转移方式等获得更优的时间,空间复杂度指标。
下面将对动态规划算法的考题进行总结。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值