剑指 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。
示例:
输入:n = 2
输出:1
输入:n = 5
输出:5
限制:
0 <= n <= 100
解题思路:
斐波那契数列的定义是 f(n+1)=f(n)+f(n−1)
,生成第 n 项的做法有以下几种:
-
递归法:
- 原理: 把
f(n)
问题的计算拆分成f(n−1)
和(n−2)
两个子问题的计算,并递归,以f(0)
和f(1)
为终止条件。 - 缺点: 大量重复的递归计算,例如
f(n)
和f(n−1)
两者向下递归需要 各自计算f(n−2)
的值。
- 原理: 把
-
记忆化递归法:
- 原理: 在递归法的基础上,新建一个长度为 n 的数组,用于在递归时存储
f(0)
至f(n)
的数字值,重复遇到某数字则直接从数组取用,避免了重复的递归计算。 - 缺点: 记忆化存储需要使用
O(N)
的额外空间。
- 原理: 在递归法的基础上,新建一个长度为 n 的数组,用于在递归时存储
-
动态规划:
- 原理: 以斐波那契数列性质
f(n+1)=f(n)+f(n−1)
为转移方程。 - 从计算效率、空间复杂度上看,动态规划是本题的最佳解法。
- 原理: 以斐波那契数列性质
动态规划解析:
-
状态定义: 设
dp
为一维数组,其中dp[i]
的值代表 斐波那契数列第i
个数字 。 -
转移方程:
dp[i+1]=dp[i]+dp[i−1]
,即对应数列定义f(n+1)=f(n)+f(n−1)
; -
初始状态:
dp[0]=0, dp[1] = 1
,即初始化前两个数字; -
返回值:
dp[n]
,即斐波那契数列的第 n 个数字。
求余运算规则: 设正整数
x, y, p
,求余符号为⊙
,则有(x+y)⊙p=(x⊙p+y⊙p)⊙p
。
复杂度分析:
- 时间复杂度
O(N)
: 计算f(n)
需循环 n 次,每轮循环内计算操作使用O(1)
。 - 空间复杂度
O(1)
: 优化后的动态规划,几个标志变量使用常数大小的额外空间。
代码:
//记忆化递归法
var result = map[int]int{}
func fib(n int) int {
if n == 0 {
return 0
}else if n==1 {
return 1
}else{
// 递归法的优化 重复遇到某数字则直接取用,避免了重复的递归计算
if _,ok:=result[n];!ok{
result[n]= fib(n-1) + fib(n-2)
}
return result[n]%1000000007
}
}
// 动态规划
func fibV2(n int) int {
dp := make(map[int]int,n)
dp[0] = 0
dp[1] = 1
for i := 2; i <= n; i++{
dp[i] = dp[i-1] + dp[i-2]
dp[i] %= 1000000007
}
return dp[n]
}
// 动态规划 优化内存版本
func fibV3(n int) int {
dpA := 0
dpB := 1
var sum int
for i := 0; i < n; i++{
sum = (dpA + dpB)%1000000007
dpA = dpB
dpB = sum
}
return dpA
}