[剑指offer刷题10] - 斐波那契数列

题目[题目及部分解法来自力扣]

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: F(0) = 0, F(1) = 1,F(N) = F(N - 1) + F(N - 2), 其中 N > 1.斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。


solution one:
相信大部分菜鸟都和我这个菜鸟一样,第一印象直接递归!
    public static int fib(int n) {
        if (n <= 1)
            return n;
        else
        {
				int a = fib(n - 1);
				int b = fib(n - 2);        	
				int result = a + b;
				return result;
		}

    }
solution two:
我们来看下非递归应该如何操作?根据斐波那契数列规则,第一个数为0,而第二个数为1,后面每个数即为前两项之和。那么我们使用a来记录第一个数,b用来记录第二个数,当n >= 2时,每次将第一个数的值赋给a,第二个数的值赋给b,而c用来记录二者的和(**这里需要注意,当进入下一次循环时,上一次循环中的c即为本次循环中的b!**),所以初始化中a = 0, b = 0, c = 1并不是固定的,符合斐波那契数列规则即可!
    public static int fib1(int n) {
        final int MOD = 1000_0000_07;
            if(n < 2) return n;
            int a = 0;
            int b = 0;
            int c = 1;
            for(int i = 2 ; i <= n ; ++i){
                a = b;   // a 来记录第一个数
                b = c;   // b 来记录第二个数
                c = (a + b) % MOD; // 第三个数开始,斐波那契数等于前两个数之和
            }
            return c;
        }

或者

public static int fib2(int n) {
{
	int a = 1;
	int b = 1;
	int c = a;
	while (n > 2)
	{
		c = a + b;//从第三个数开始,斐波那契数等于前两个数的和;
		a = b;//将前一个数给到a,开始下一次求值
		b = c;//将斐波那契数给b,开始下一次求值
		n--;//每求一次,n都要减一
	}
	return c;
}
solution three:
动态规划:
  • 状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表 斐波那契数列第  i 个数字 。
  • 转移方程: dp[i + 1] = dp[i] + dp[i - 1],即对应数列定义 f(n + 1) = f(n) + f(n - 1)
  • 初始状态: dp[0] = 1dp[0]=1, dp[1] = 1dp[1]=1 ,即初始化前两个数字;
  • 返回值: dp[n] ,即斐波那契数列的第 n 个数字。
    public int numWays(int n) {
        if(n < 2) return n;
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2;i <= n;i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
solution two:
[矩阵快速幂](https://blog.csdn.net/lzyws739307453/article/details/90144987?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_ecpm_v1~rank_v31_ecpm-4-90144987.pc_agg_new_rank&utm_term=%E7%9F%A9%E9%98%B5%E5%BF%AB%E9%80%9F%E5%B9%82%E8%AF%A6%E8%A7%A3&spm=1000.2123.3001.4430):(注:以下快速幂知识来源于csdn,点击“矩阵快速幂”即可跳转!)
![在这里插入图片描述](https://img-blog.csdnimg.cn/e24972e7143647cda0e45aacbe61b67f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATmljay1ZaW4=,size_20,color_FFFFFF,t_70,g_se,x_16)

所谓的矩阵快速幂,就是将快速幂方法中的变量换成了矩阵即为矩阵快速幂。快速幂:一般的,我们都知道求只需要连续乘3次2就能得到,那么等于多少呢?其实这个一很简单,不就是13个2相乘吗,连续乘13次2就行了。那么,呢? 是不是要连续乘100次、1000次,我们将这类问题归结为求。那么当b很大的时候,是很浪费时间的,往往会造成超时,那有没有更快的计算方法呢?当然了,就是快速幂! 我们以b=13为例,将b表示为二进制: ![在这里插入图片描述](https://img-blog.csdnimg.cn/12b31d0049884aa398500b9368bbab95.png) 那么我们观察只要b的2进制的第i位为1,就乘上a^{2^i}(这个值可以在循环中得到)。 大家可以发现,这样计算要比普通的连乘计算要简单快速的多,如果你要计算2^{10000}, 连乘要计算10000次,而快速幂大概要计算log_210000次。这就是快速幂算法,它将幂次运算由O(n)简化到了O(log^2n).
快速幂充分利用了二进制的特点(非0即1),把十进制转化成二进制后再展开,对于每一位的当前结果要么乘要么不乘,把原本n次的循环变成了n的二进制位数log^2n的循环。

那么,斐波那契数列利用矩阵快速幂代码如下,其中pow和multiply即为矩阵快速幂模板代码!
    static final int MOD = 1000000007;

    public int fib3(int n) {
        if (n < 2) {
            return n;
        }
        int[][] q = {{1, 1}, {1, 0}};
        int[][] res = pow(q, n - 1);
        return res[0][0];
    }

    public int[][] pow(int[][] a, int n) {
        int[][] ret = {{1, 0}, {0, 1}};
        while (n > 0) {
            if ((n & 1) == 1) {   //判定表达式表示奇偶性,==1为奇数,==0为偶数
                ret = multiply(ret, a);   //为奇数的时候和单位矩阵相乘!
            }
            n >>= 1;  //二进制右移操作,等价于n /= 2,二进制8421,各位之间是2倍的关系
            a = multiply(a, a); //偶数的时候乘以本身
        }
        return ret;
    }

    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;
    }

位运算回顾!
  • https://www.runoob.com/w3cnote/bit-operation.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值