斐波那契数列
大家都知道斐波那契数列,
现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
- 用列表循环增加 fibonacci series 的下一项, 最后
pop()
第n项
# -*- coding:utf-8 -*-
class Solution:
def Fibonacci(self, n):
# write code here
a = [0,1,1]
if n <3:
return a[n]
for i in range(3,n+1):
a.append(a[i-1]+a[i-2])
return a.pop()
【tips】:
- 题目要求有第0项为0
- for loop 边界
- 用a,b分别存前两项,循环
a,b = b,a+b
推到第n项直接输出。
# -*- coding:utf-8 -*-
class Solution:
def Fibonacci(self, n):
# write code here
a,b = 0,1
while n > 0:
a,b = b,a+b
n -= 1
return a
【tips】:
- 理解
a,b = b,a+b
-
return a
/return b
?
跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
- 倒推来分解问题,最后跳法个数可以分解为倒数两级是怎么跳的, 也就是说
f(n) = f(n-1) + f(n-2)
。 确定是斐波那契数列。 解法同上
# -*- coding:utf-8 -*-
class Solution:
def jumpFloor(self, number):
# write code here
'''
f(n) = f(n-1) + f(n-2)
f(1) = 1
f(2) = 2
'''
res = [None,1,2] #None为占位
if number < 3:
return res[number]
for i in range(3,number+1):
res.append(res[i-1] + res[i-2])
return res[number]
【tips】:
- 分析问题场景是关键步骤,从结果开始倒推
- 没提第0项所以我用了
None
占个位置,写别的也可以 - for loop 边界
a,b = b,a+b
解法,同上
# -*- coding:utf-8 -*-
class Solution:
def jumpFloor(self, number):
# write code here
a = 1
b = 1
for i in range(number):
a,b = b,a+b
return a
【tips】:
- 理解
a,b = b,a+b
-
return a
/return b
?
变态跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
从最初状态尝试推算
f(1) = 1
f(2) = f(2-1) + f(2-2) //f(2-2)表示第一次跳2阶之后有f(2-2)种跳法。
f(3) = f(3-1) + f(3-2) + f(3-3)
...
f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)
简化
f(n-1) = f(0) + f(1) + f(2) + f(3) + ... + f((n-1)-1)
= f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
f(n) = 2*f(n-1)
总结
| 0 ,(n=0 ) 没台阶
f(n) = | 1 ,(n=1 ) 一个台阶为1种
| 2*f(n-1) = 2^(n-1),(n>=2)
解
# -*- coding:utf-8 -*-
class Solution:
def jumpFloorII(self, number):
# write code here
if number <= 0:
return 0
else:
return pow(2, number - 1)
【tips】:
- 因为每次可跳台阶数不定,从头开始推公式
- 简化过程是关键
矩形覆盖
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法?
比如 n=3 时,23的矩形块有3种覆盖方法:
和跳台阶类似,把n分成不同排列组合的2和1
f1 -- 1
f2 -- 2
f3 -- 3
f4 -- 5 -- f3 + f2
fibonacci!
解
class Solution:
def rectCover(self, number):
# write code here
if number==0:
return 0
elif number==1:
return 1
a,b=1,1
while number>1:
a,b=b,a+b
number-=1
return b
【tips】:
- return b
斐波那契字符数
Tom发现了一种神奇的字符串-斐波那契字符串,
定义 f[1]=0 , f[2]=1 ,
对于所有的i>2都有f[i]=f[i-2]+f[i-1],其中“+”代表拼接,
比如01+10=0110,
现在对于字符串f[n],请判断f[n]的第k项是0,还是1。
输入两个数字n和k(n<=300,k<=1000000000)。
输出f[n]的第k位,如果k大于f[n]的位数,则输出-1。
- 直接,超时超内存
自底向上地构建出从第一个字符串f[1]直到最后的f[n],然后查看f[n]的第k项是0还是1。
由于斐波那契数递增速度非常快,当n较大时,这种暴力拼接法无法完成。
- 充分利用斐波那契数列的性质,自顶向下对问题逐步剪枝,定位需要判断的数字位置。
比如,对输入n=19, k=98:
先算出n=19对应的字符串长度len,也就是斐波那契数列的第19项。考虑到k是int类型,故可以先算出n=1~47对应的斐波那契数组成的数组(n=47时,斐波那契数大于2^31-1,无需再提前计算更多的斐波那契数),然后直接在计算好的斐波那契数组中取出第19项。
找到n-2=17,n-1=18对应的斐波那契字符串长度(也就是第17/18个斐波那契数)len1,len2。同样也是在提前计算好的斐波那契数组中找到。
对于k=98,如果k>len1,说明要找的数字在n=18对应的斐波那契字符串中,也就是n=19对应的斐波那契字符串的后半部分;如果k<=len1,说明要找的数字在n=17对应的斐波那契字符串中,也就是n=19对应的斐波那契字符串的前半部分。
根据判断结果,将n赋值为n-2或n-1,同时将k赋值为k或(k-len1),完成剪枝。回到第1步,递归向下搜索。直到n=1或者2,这时k也变为1,定位完成,结束递归,直接返回结果。
进一步优化:
当输入的n > 47时,斐波那契字符串的长度超出了int型变量k的表示范围。需要提前进行推断剪枝。你能根据k的int表示范围(小于等于2^31-1)这一特性,对n较大的那些用例进行优化么?
- 递归
这个问题可以使用递归的方法来求解。
题目中给出了字符串拼接的规律:f[i]=f[i-2]+f[i-1]。可以看出对于第i个字符串,它的前半部分属于第i-2个字符串,后半部分属于第i-1个字符串。
这样,当给定具体的n和k时,我们可以首先判断k位于第n个字符串的前半部分还是后半部分,然后递归的把k传递给第n-2或n-1个字符串。
计算过程:
为了判断属于前半部分还是后半部分,需要计算每一个字符串的长度。
一个len数组。每个位置为对应字符串的长度。
len[1] = len[2] = 1;
len[i] = len[i-2]+len[i-1];
如果k > len[n],则输出-1。
设置递归函数 int find(int i, int k);这个函数返回第i个字符串中k个位置的字符
判断k在前半部分还是后半部分。
a) K <= len[i-2] 前半部分,调用find(i-2, k)
b) K > len[i-2] 后半部分,调用find(i-1, k-len[i-2])
上面的计算在第一步时会遇到问题,当n大于几十的时候,len就会超出int的范围。
对这个问题的解决基于对递归过程的观察,第4步a)中k始终是不变的,i的奇偶性也保持不变。所以在计算第1步len时可以提前停止,当发现len[i]>=k且i与n的奇偶性相同时。第4步中的递归起始也可以使用第i个字符串开始,而不是第n个。
时间复杂度O(2*n)
空间复杂度O(n)
Reference
[1] 牛客网在线编程 - 剑指Offer