(例题1)斐波那契数列
写⼀个函数,输⼊ n ,求斐波那契(Fibonacci)数列的第 n 项。
斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数
相加⽽得出。
0 1 1 2 3 5 8 13 ··
注意:这里输出的是Fibnacci数列的第n项,而不是前n项之和
def f(n):
if n==0:
return 0
elif n==1:
return 1
else:
return f(n-1)+f(n-2)
k=f(6)
print(k)
上面这段代码的意思是
(1)递推关系(recurrentce relation):所有的f(n)都可以被拆成 前一项 f(n-1) 和前两项 f(n-2) 的和
(2)基本情况(bottom cases):递归到最后最基本的组成要素,那就是F(0) = 0, F(1) = 1, 由着两个元素不断的相加就可以衍生出后面所有的可能的值‘
衍生题目’
求Fibonacci数列中前n个元素之和(暂时不会做)
(例题2)爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的⽅法可以爬到楼顶 呢? 注意:给定 n 是⼀个正整数。下面给出了n=1到5所有的组合的可能
台阶数 总共可能的路径数 所有可能的路径
n=1 jump(n)=1 ——1
n=2 jump(n)=2 ——1 1 / 2
n=3 jump(n)=3 ——1 1 1 / 1 2 / 2 1
n=4 jump(n)=5 ——1 1 1 1/ 1 1 2/ 2 1 1/ 2 2/ 1 2 1
n=5 jump(n)=8 ——1 1 1 1 1/ 1 1 1 2/ 1 1 2 1/ 1 2 1 1 / 2 1 1 1 / 1 2 2 / 2 1 2 / 2 2 1 /
注意:这里是让你你去 求 的 是 有多少种组合
首先咱们不瞎逼逼,直接上答案,从答案中总结做题的思路和解题的规律。
逼逼再多,扣上答案,自己顺着思路写不出来,也是白费。
对于零基础小白,能用自己的思路理解题目,顺着自己的思路能把答案写出来才是王道。
如果你不是科班出身,没学过“算法数据结构”,如果你完全不看答案、自己在那里天马行空的空想,你的思路往往就很奇怪且低效。因为,没经过专业训练的你,想不到正地方上去。
想要培养“科班的”“算法数据结构”思维,只有一条路,那就是多看答案。首先通过举例子,穷举法来理解答案这样设计的合理性。其次,多对答案思路进行提炼和总结,最后用你的话能整个题目的做题思路串起来,然后顺着你写的思路注释,最终把答案代码写出来
def f(n):
# 这段if elif想表达是bottom case
# 分别是上一个台阶有几种可能性以及上两个台阶有几种可能性
if n==1:
return 1
# 上一个台阶,只有“走一步”这一种上去的方式
elif n==2:
return 2
# 上两个台阶,有“走一步,再走一步”和“一次走两步”这两种走法
else:
return f(n-1)+f(n-2)
# f(5) = f(4)+ f(3)
# f(5) = f(3)+ f(2) + f(2)+ f(1)
# f(5) = f(2) +f(1) + f(2) + f(2)+ f(1)
# f(5) = 2 + 1 + 2 + 2 + 1
# f(5) = 8
print(f(n))
所有的递归都是一个套路,递归是由两个要素组成的。
要素(1):找到每次自己连续地、“不确定次数”调用自己(将目前问题拆成更小的问题) 的那个 调用的通用公式,
这个对应的是下面调用代码的这一句“return f(n-1)+f(n-2) ”
f(n) = return f(n-1)+f(n-2) 这句代码,想表达的思路是:上到目前这个台阶有两种可能性,一种可能是从下面一个台阶上来的,另一种从下面两个台阶上来。那么我们只需要将这两种可能性 各自的 可能的走的方式的数量加在一起,就可以得到所有的可能的走的方式
也就是说整个可能的步数组合的方式,这个求f(n)的大问题,就被拆解成了求f(n-1)加上f(n-2) 这个两个子问题
——这两个子问题又可以进一步被拆分为 f(n-2)+f(n-3)加上 f(n-3)+f(n-4)这四个子问题
——那么,问题来了,是否会这样子子代代无穷矣地就这么拆下去呢?
——肯定不会啊!如果是无穷次的递归下去,电脑不死机了吗?我们的问题也没解决啊
—— 当问题下钻到f(1)=1, f(2)=2的时候,递归循环终止了。
——最后根据递归循环的次数来组合f(1)=1和f(2)=2,得出你要的答案f(5) = 8
有的同学会觉得不应该是f(n-1)加f(n-2),而应该是f(n-1)乘f(n-2)。为了验证是“加”,不是“乘”,下面演示给你看。
——最后一行是f(n)这里是f(5)的爬楼梯各种组合的个数
——通过观察你可以发现f(n), 这里是f(5), 是由蓝色的f(n-1)和黄色的f(n-2)组成的。
——至此,成功验证了f(n) = f(n-1)+f(n-2)这个递归循环的通用公式这一公式的合理之处
要素(2):不停地自己调用自己,一直往下挖,挖到头了,再往下挖不动了的那些情况。不能再继续往下调用的bottom case。实际上也就是这些bottom case作为基本要素的拼装组合,才形成了最后的答案。
(2.1)首先我们要求出bottom case的函数值是多少?
首先,bottom case求的还是f(n),也就是bottom case求的还是上n个台阶有多少种走法
在这里,bottom case是n==1或n==2的时候,也就是f(1)和f(2)是bottom case。
n=1 jump(n)=1 ——1 一步走上去这一种可能性
n=2 jump(n)=2 ——1 1 / 2 “走一步,再走一步” 和“一次走两步”,2种可能性
也就是f(1)=1; f(2)=2
在上面那个图中 粉色的表示f(1)=1, 绿色的表示f(2)=2
前面我们讲了f(n)可以被分解为多个f(1)=1和多个f(2)之和的方式,具体多少个f(1)和多少个f(2)取决于递归的次数。下面展示一下,以防你不信
首先是演算
# f(5) = f(4)+ f(3)
# f(5) = f(3)+ f(2) + f(2)+ f(1)
# f(5) = f(2) +f(1) + f(2) + f(2)+ f(1)
# f(5) = 2 + 1 + 2 + 2 + 1
# f(5) = 8
从中可以发现,f(5)是由3个f(2)和2个f(1)求和得到的。
图形化表示是下面这个图
也就是说f(n)最终求得数值,就是通过 多个f(2)=2和多个f(1)=1求和和组合得到的。也就是说f(2)和f(1)是f(n)组成的必要成分elements 。至于多少个f(2)和多少个f(1)这取决于你的递归次数,也就是n的取值
(例题3)正整数拆成多个1和3的相加求和
给正整数N,分解成1和3的组合。这里的组合指的是相加求和,而不是因式分解。⽐如N=4,分解为1111,13,31(递归)
s=""
def gene(n,s):
"""
n -> 既是(1)剩余待填满的字符串的长度(字符串由几个数字组成),也是(2)被求和拆分的那个数字,比如被拆成3和1的那个4
s -> 初始化字符串的内容,一般是一个空的字符串
"""
# 如果n(剩余待填满的字符串的长度),在减三和减一的操作后 等于0或小于0了,那就说明“该填满的字符串长度”都已经填满了,任务完成,可以退出函数了
# 等于0,说明目前这个字符串组合可以存进去
if n==0:
result.append(s)
return
# 小于0,说明没啥可存了,可以直接退出了
elif n<0:
return
# ————————这个位置——————————
# 如果前面两个条件都不满足也就是说 n>=1 函数不会终止,会继续执行后面的代码
s1=s+str(1)
gene(n-1,s1)
s2=s+str(3)
gene(n-3,s2)
n=4
# n表示被拆分的数字,这里是4
# s表示??暂时被拆分的东西?
result=[]
gene(n=4,s="")
print(result)
疑问
是通过怎么样的方式让,这个数字拆成3和1的组合的?
(例题4)括号生成
数字 n 代表⽣成括号的对数,请你设计⼀个函数,⽤于能够⽣成所有可能的并 且 有效的 括号组合。
• 示例 1: 输⼊:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]。输出的是3个括号对可能的组合
• 示例 2: 输⼊:n = 1 输出:["()"]
result=[]
s=""
def gene(n,l,r,s):
"""
l记录左边的括号数量
r记录右边的括号数量
"""
# 当左右两边的括号数,都同时到达n,将s这个位置的数据存进result里面
if l==n and r==n:
result.append(s)
return
# 如果左边的括号数量少,就在左边加括号
if l<n:
s1=s+"("
# 左边的括号多一个,表示左边括号个数的l也要加一
gene(n,l+1,r,s1)
# 如果右边括号数量小,就往右边加括号
if l>r:
s2=s+")"
# 右边的括号多一个,表示右边括号个数的l也要加一
gene(n,l,r+1,s2)
n=3
gene(n,0,0,s)
print(result)
(例题5)全排列-不含重复数字
给定⼀个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按 任意顺序 返回答案。
Leetcode-46
输⼊:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]。
输⼊:nums = [0,1] 输出:[[0,1],[1,0]
result=[]
def gene(nums,l):
if len(l)==len(nums):
result.append(l)
return
for a in nums:
if a not in l:
l1=l+[a]
gene(nums,l1)
nums=[1,2,3]
gene(nums,[])
print(result)
不太懂
(例题6)全排列-含重复数字
给定⼀个含重复数字的数组 nums ,返回其 所有可能的全排列。(输出数组的长度要和输入长度是一样的) 。你可以 按任 意顺序 返回答案。
输⼊:nums = [2,2,3]
输出: [[2, 2, 3], [2, 3, 2], [3, 2, 2]]
result=[]
def gene(nums,l,m):
if len(l)==len(nums) and l not in result:
result.append(l)
return
for i in range(len(nums)):
if i not in m:
l1=l+[nums[i]]
m1=m+[i]
gene(nums,l1,m1)
nums=[2,2,3]
gene(nums,[],[])
print(result)
真是没看懂
(例题7)求组合数
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
输⼊: n = 4, k = 2。 return 1到4中,所有可能的2个数的组合。
输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
res=[]
def gene(n,k,l,start): # start 开始取数的位置
"""
n表示数字的可能性是1到n
k表示一个组合的数字的数量
l表示初始的数组,一般是空列表
start表示从哪个数字开始,一般是1
"""
# 如果l这个列表的长度和要求的组合长度k相等,则说明组合完毕了,把最后一个结果放进result,就结束即可
if len(l)==k:
res.append(l)
return
# 如果还没到达就继续组合
# ????这段没看懂???
for i in range(start,n+1):
gene(n, k, l+[i], i+1)
n=4
k=2
gene(n,k,[],1)
res # [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
有一段没看懂
如何学算法数据结构的题目?
(1)上面给出的例题,你能能过一张白纸扣上答案,通过自己的思考写出来。如果你通过自己的思考写不出来,你就把你这个思维链条上断开的地方拿着去找老师,问他为什么就能想出来、你为什么想不出来,这就是你和老师之间的差距,这也就是你知识上的欠缺
(2)去牛客网或力扣网,找这个 专题(如递归专题)的最简单的习题去做。同样的,不会的,去问老师。