斐波那契数列
(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
一、循环法:
// n 太大会溢出,可以使用数据位更宽的数据类型
func fib(n int) int {
if n == 1 || n == 2 {
return 1
}
f1 := 1
f2 := 1
res := 0 //存放结果
for i := 3; i <= n; i++ {
res = f1 + f2
f2 = f1
f1 = res
}
return res
}
时间复杂度:O(n)
空间复杂度:O(1)
二、递归法:
根据递推公式:F(n)=F(n - 1)+F(n - 2)可直接得出
func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}
时间复杂度:因为成二叉树形式 ,所以时间复杂度为O(2^n)
空间复杂度:树约有n-1层,所以空间复杂度为O(n)
三、动态规划法:
- 确定 dp数组以及下标的含义:dp[i] 的定义为:第 i 个数的斐波那契数值是 dp[i]
- 确定递推公式:题⽬已经把递推公式直接给我们了:状态转移⽅程 dp[i] = dp[i - 1] + dp[i - 2]
- 初始化dp数组: dp[0] = 0, dp[1] = 1
- 确定遍历顺序: dp[i] 是依赖 dp[i - 1] 和 dp[i - 2] ,所以遍历的顺序是从前到后遍历
func fib(n int) int {
if n < 2 {
return n
}
dp := make([]int, n+1)
dp[0] = 0
dp[1] = 1
for i := 2; i <= n; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n]
}
时间复杂度:O(n)
空间复杂度:O(n)
根据斐波那契数列的性质,我们可以用 滚动数组来优化一下空间复杂度
func fib(n int) int {
if n < 2 {
return n
}
dp := []int{0, 1}
result := 0
for i := 2; i <= n; i++ {
result = dp[0] + dp[1]
dp[0] = dp[1]
dp[1] = result
}
return dp[1]
}
时间复杂度:O(n)
空间复杂度:O(1)
方法四:快速矩阵幂
定义一个矩阵:
可以找到一个矩阵M(这里可以理解为从一个状态转移到另一个状态),使得:
设:
所以
由矩阵乘法可以得到:
af(n)+bf(n-1)=f(n+1)
cf(n)+df(n-1)=f(n)
最终求得:
由递推公式最终得到:
因此只要我们能快速计算矩阵 M 的 k 次幂,就可以得到 F(n)的值。
此处需要用到快速幂的算法,不明白的可以看一下快速幂算法_Sakeyuan的博客-CSDN博客:
//矩阵相乘
func arrayMultiply(a, b [][]int) [][]int {
c := [][]int{{0, 0}, {0, 0}}
for i := 0; i < 2; i++ {
for j := 0; j < 2; j++ {
c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j]
}
}
return c
}
// Matrix fast power
func arrayPow(a [][]int, n int) [][]int { //矩阵快速幂
ret := [][]int{{1, 0}, {0, 1}} //单位矩阵 ,储存结果
for n > 0 {
if n&1 == 1 {
ret = arrayMultiply(ret, a)
}
n >>= 1
a = arrayMultiply(a, a)
}
return ret
}
func fib(n int) int {
if n < 2 {
return n
}
m := [][]int{{1, 1}, {1, 0}}
result := arrayPow(m, n)
return result[0][1] // 由递推公式可以看出f(n)为 result[0][1]位置
}
时间复杂度:O(logn)
空间复杂度:O(1)
五、通项公式:
根据递推公式 F(n)=F(n - 1)+F(n - 2) 可以得出特征方程:
求得:
通解为:
带入f(0)=0,f(1)=1,解得:
最终得到通项公式:
func fib(n int) int {
sqrt5 := math.Sqrt(5)
fibN := math.Pow((1+sqrt5)/2, float64(n)) - math.Pow((1-sqrt5)/2, float64(n))
return int(math.Round(fibN / sqrt5)) /*round()函数四舍五入*/
}