剑指Offer----递归-----Python

尊重知识产权:

掘金—程序员吴师兄: 看动画轻松理解「递归」与「动态规划」

人们往往习惯了平铺直叙的思维方式,所以递归与动态规划这种带循环概念的比较绕,相对难理解。

什么是递归

先下定义:递归算法是一种直接或者间接调用自身函数或者方法的算法。

通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。它有如下特点:

    1. 一个问题的解可以分解为几个子问题的解
    1. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
    1. 存在递归终止条件,即必须有一个明确的递归结束条件,称之为递归出口

在这里插入图片描述

1 一个问题的解分解为几个子问题的解

子问题就是相对与其前面的问题数据规模更小的问题。

在动图中①号问题(一块大区域)划分为②号问题,②号问题由两个子问题(两块中区域)组成。

2 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样

「①号划分为②号」与「②号划分为③号」的逻辑是一致的,求解思路是一样的。

3 存在递归终止条件,即存在递归出口

把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能存在无限循环,这就需要有终止条件。

①号划分为②号,②号划分为③号,③号划分为④号,划分到④号的时候每个区域只有一个不能划分的问题,这就表明存在递归终止条件。

从递归的经典示例开始干活

1 数组求和

在这里插入图片描述

def sum(list):
    if list==[]:
        return 0
    return list.pop(0)+sum(list)

sum_ = sum([1,2,3,4])
print(sum_)

步骤:

Sum(arr[0...n-1]) = arr[0] + Sum(arr[1...n-1])

后面的 Sum 函数要解决的就是比前一个 Sum 更小的同一问题。

Sum(arr[1...n-1]) = arr[1] + Sum(arr[2...n-1])

以此类推,直到对一个空数组求和,空数组和为 0 ,此时变成了最基本的问题。

Sum(arr[n-1...n-1] ) = arr[n-1] + Sum([])
2 汉诺塔问题

汉诺塔(Hanoi Tower)问题也是一个经典的递归问题,该问题描述如下:

汉诺塔问题:古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个和尚想把这个盘子从A座移到B座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。现要求将A塔座上的64个圆盘移到c塔座上并按同样顺序叠排,问需要移动圆盘多少次?

在这里插入图片描述
在这里插入图片描述

① 如果只有 1 个盘子,则不需要利用 B 塔,直接将盘子从 A 移动到 C 。

② 如果有 2 个盘子,可以先将盘子 2 上的盘子 1 移动到 B ;将盘子 2 移动到 C ;将盘子 1 移动到 C 。这说明了:可以借助 B 将 2 个盘子从 A 移动到 C ,当然,也可以借助 C 将 2 个盘子从 A 移动到 B 。

③ 如果有 3 个盘子,那么根据 2 个盘子的结论,可以借助 C 将盘子 3 上的两个盘子从 A 移动到 B ;将盘子 3 从 A 移动到 C ,A 变成空座;借助 A 座,将 B 上的两个盘子移动到 C 。

④ 以此类推,上述的思路可以一直扩展到 n 个盘子的情况,将将较小的 n-1个盘子看做一个整体,也就是我们要求的子问题,以借助 B 塔为例,可以借助空塔 B 将盘子A上面的 n-1 个盘子从 A 移动到 B ;将A 最大的盘子移动到 C , A 变成空塔;借助空塔 A ,将 B 塔上的 n-2 个盘子移动到 A,将 C 最大的盘子移动到 C, B 变成空塔。。。

算法一-----数学归纳:

(1)n == 1

​       第1次 1号盘 A---->C sum = 1 次

​ (2) n == 2

​       第1次 1号盘 A---->B

​        第2次 2号盘 A---->C

​       第3次 1号盘 B---->C sum = 3 次

(3)n == 3

第1次 1号盘 A---->C

第2次 2号盘 A---->B

第3次 1号盘 C---->B

第4次 3号盘 A---->C

第5次 1号盘 B---->A

第6次 2号盘 B---->C

第7次 1号盘 A---->C sum = 7 次

不难发现规律:1个圆盘的次数 2的1次方减1

2个圆盘的次数 2的2次方减1

​ 3个圆盘的次数 2的3次方减1

​ 。 。 。 。 。

​ n个圆盘的次数 2的n次方减1

故:移动次数为:2^n - 1

def move_num(n):
    return 2^n - 1

算法二----递归:

如果n!=1,首先将n-1个盘子移到B,然后将第n个盘子移到C;将n-2个盘子移到A,将第n-1个盘子移到C;重复这个过程,直到只有一个盘子。

class num:
	def __init__(self):
		self.i = 1
		

	def  Hanoi(self,n,a,b,c):
		if n == 1:
			print(a,'--->',c)
		else:
			self.Hanoi(n-1, a, c, b)#将前n-1个盘子移到b上
			self.i +=1
			# print(a,'--->',c)#将最底下的一个盘子从a移动到c
			self.Hanoi(n-1, b, a, c)#将b上的n-1个盘子移到c上
			self.i +=1
			print(self.i)

num = num()
num.Hanoi(3, "a", "b", "c")
3 爬台阶问题

问题:一个人爬楼梯,每次只能爬1个或2个台阶,假设有n个台阶,那么这个人有多少种不同的爬楼梯方法?

先从简单的开始,以 4 个台阶为例,可以通过每次爬 1 个台阶爬完楼梯:

在这里插入图片描述

可以通过先爬 2 个台阶,剩下的每次爬 1 个台阶爬完楼梯
在这里插入图片描述

在这里,可以思考一下:可以根据第一步的走法把所有走法分为两类:

  • ① 第一类是第一步走了 1 个台阶
  • ② 第二类是第一步走了 2 个台阶

所以 n 个台阶的走法就等于先走 1 阶后,n-1 个台阶的走法 ,然后加上先走 2 阶后,n-2 个台阶的走法。

用公式表示就是:

f(n) = f(n-1)+f(n-2)

有了递推公式,递归代码基本上就完成了一半。那么接下来考虑递归终止条件。

当有一个台阶时,我们不需要再继续递归,就只有一种走法。

所以 f(1)=1

通过用 n = 2n = 3 这样比较小的数试验一下后发现这个递归终止条件还不足够。

n = 2 时,f(2) = f(1) + f(0)。如果递归终止条件只有一个f(1) = 1,那 f(2) 就无法求解,递归无法结束。
所以除了 f(1) = 1 这一个递归终止条件外,还要有 f(0) = 1,表示走 0 个台阶有一种走法,从思维上以及动图上来看,这显得的有点不符合逻辑。所以为了便于理解,把 f(2) = 2 作为一种终止条件,表示走 2 个台阶,有两种走法,一步走完或者分两步来走。

总结如下:

  • ① 假设只有一个台阶,那么只有一种走法,那就是爬 1 个台阶
  • ② 假设有两个个台阶,那么有两种走法,一步走完或者分两步来走
    在这里插入图片描述

通过递归条件:

f(1) = 1;
f(2) = 2;
f(n) = f(n-1)+f(n-2)

复制代码

很容易推导出递归代码:

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}
复制代码

通过上述三个示例,总结一下如何写递归代码:

  • 1.找到如何将大问题分解为小问题的规律
  • 2.通过规律写出递推公式
  • 3.通过递归公式的临界点推敲出终止条件
  • 4.将递推公式和终止条件翻译成代码

剑指offer–递归

裴波那契数列

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。(n<=39)

尊重知识产权

百度百科:斐波那契数列

图:

在这里插入图片描述

def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fib(n-1) + fib(n-2)

跳台阶

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

f(1)=1, f(2)=f(1)+f(0),f(0)=2

f(n)=f(n-1)+f(n-2)

同上一个题

变态跳台阶

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

这只青蛙了不起,能跳。

思路:

如果一只青蛙一次可以跳上1级台阶,也可以跳上2级,求n

f(n)=f(n-1)+f(n-2)

如果一只青蛙一次可以跳上1级台阶,也可以跳上2级,也可以跳上3级,求n

f(n)=f(n-1)+f(n-2)+f(n-3)

如果一只青蛙一次可以跳上1级台阶,也可以跳上2级,它也可以跳上n级,求跳法

f(n)=f(n-1)+f(n-2)+...+f(0)

其实这个算法没有错,但是复杂度比较高。

数学归纳也可以:

  • 当n=1时,结果为1;
  • 当n=2时,结果为2;
  • 当n=3时,结果为4;

以此类推,我们使用数学归纳法不难发现,跳法f(n)=2^(n-1)。

def num(n):
     if n <= 0:
            return 0
        else:
            return pow(2,n-1)

矩阵覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

f(3)=f(2)+f(1)

f(n)=f(n-1)+f(n-2)

class Solution:
    def rectCover(self, number):
        # write code here
        if number == 0:
            return 0
        if number ==1:
            return 1
        a,b=1,1
        while number>1:
            a,b=b,a+b
            number-=1
        return b

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值