题目
剑指 Offer 10- I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
来源:力扣(LeetCode)
分析:
来源:力扣
思路:
我们先跟据F(N) = F(N - 1) + F(N - 2)构建出的矩阵关系,得出M。然后使用快速幂算法求出M^n。
怎么用用快速幂算法求出M^n?先来看个栗子:
3^10=3*3*3*3*3*3*3*3*3*3
=9^5 = 9^4*9
=81^2*9
=6561*9
基于以上原理,我们在计算一个数的多次幂时,可以先判断其幂次的奇偶性,然后:
- 如果幂次为偶直接 base(底数) 作平方,power(幂次) 除以2
- 如果幂次为奇则底数平方,幂次整除于2然后再多乘一次底数
对于涉及到 [判断奇偶性] 和 [除以2] 这样的操作。使用系统的位运算比普通运算的效率是高的,因此可以进一步优化:
- 把
power % 2 == 1变为(power & 1) == 1 - 把
power = power / 2变为power = power >> 1
关于矩阵快速幂算法,推荐看:
解法:
这里只提供一种思路,即矩阵快速幂,时间复杂度为O(logn)。
class Solution{
static final int MOD = 1000000007;
//矩阵快速幂
public int fib(int n) {
if (n < 2) {
return n;
}
//定义乘积底数
int[][] q = {{1, 1}, {1, 0}};
//定义幂次
int[][] res = pow(q, n - 1);
//按照公式,返回的是两行一列矩阵的第一个数
return res[0][0];
}
//定义函数,求底数为 base 幂次为 power 的结果
public int[][] pow(int[][] a, int n) {
//定义变量,存储计算结果,此次定义为单位阵
int[][] ret = {{1, 0}, {0, 1}};
//可以一直对幂次进行整除
while (n > 0) {
//1.若为奇数,需多乘一次 base
//2.若power除到1,乘积后得到res
//此处使用位运算在于效率高
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
//不管幂次是奇还是偶,整除的结果是一样的如 5/2 和 4/2
//此处使用位运算在于效率高
n >>= 1;
a = multiply(a, a);
}
return ret;
}
//定义函数,求二维矩阵:两矩阵 a, b 的乘积
public int[][] multiply(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = (int) (((long) a[i][0] * b[0][j] + (long) a[i][1] * b[1][j]) % MOD);
}
}
return c;
}
}