重温斐波那契数列

写在前面

之前写过一个快速斐波那契python版本的,今天本来想来写个C++版本的快速斐波那契,然后又没写出来,,,,(在线卑微T_T)。所以就借着这个时间,用C++来整理一下整数快速幂,矩阵快速幂以及快速斐波那契的知识吧。

part I 整数快速幂

在之前的博客python写简单的整数快速幂和矩阵快速幂里面,我给过两个简单的例子,可以很容易看清楚算法的套路,然后具体的分析也稍微写过,在这里,分析传送门。这些用的都是python语言实现的,接下来我们换成C++语言整理一下思路。

先举个例子,假如我们计算 a n a^n an,在没有正负约束等情况下,比如我们要算 a 11 a^{11} a11
按照最简单的就是 a ∗ a ∗ a ∗ a ∗ a ∗ a ∗ a ∗ a ∗ a ∗ a ∗ a a*a*a*a*a*a*a*a*a*a*a aaaaaaaaaaa,这样要算10次乘法运算。
11 11 11的二进制为 1011 1011 1011
a 11 = a 8 ∗ a 2 ∗ a 1 a^{11}= a^8*a^2*a^1 a11=a8a2a1
我们假设最后结果存在 r e s res res变量里面,开始时候 r e s = 1 res=1 res=1
第一圈: r e s = r e s ∗ a 1 = a res=res*a^1=a res=resa1=a,把基数a变成 a 2 = a 2 a^2=a^2 a2=a2,因为此时1011要右移一位变成101
第二圈: r e s = r e s ∗ a 2 = a 3 res=res*a^2=a^3 res=resa2=a3,把基数变成 ( a 2 ) 2 = a 4 (a^2)^2=a^4 (a2)2=a4,101右移一位变成10,
第三圈:由于二进制10的最后一位不是1,所以这一圈不必res*,只要把基数变为 ( a 4 ) 2 = a 8 (a^4)^2=a^8 (a4)2=a8,10右移一位变成1.
第四圈: r e s = r e s ∗ a 8 = a 11 res=res*a^8=a^{11} res=resa8=a11,把基数变成 ( a 8 ) 2 = a 16 (a^8)^2=a^{16} (a8)2=a16,1右移一位变成0
第五圈:跳出循环
所以在计算 a 11 a^{11} a11的时候,我们只算了4次,数据越庞大效果越明显。在每一圈结束的时候,一定要将基数变成基数的平方。当然这里还是没有考虑负数,大数,等问题,接下来看个题目。

我们拿剑指 Offer 16. 数值的整数次方来说,题目如下:

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x^n)。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:
输入:x = 2.10000, n = 3
输出:9.26100

示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2^-2 = 1/2^2 = 1/4 = 0.25

提示:
-100.0 < x < 100.0
-2^31 <= n <= 2^31-1
-10^4 <= x^n <= 10^4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析:

  • 给了 x x x的范围是有正有负的,包含零,需要考虑。
  • n n n的范围也很大, − 2 31 < = n < = 2 31 − 1 -2^{31} <= n <= 2^{31}-1 231<=n<=2311,当 n = − 2 31 n=-2^{31} n=231时候,如果用整型直接取相反数会越界,会报错,所以得定义一个新的long类型变量。
  • 考虑在 n n n为负值的时候的幂运算是怎么实现的
    直接上代码看:
class Solution {
public:
    double myPow(double x, int n) {
        if(!n || !x) return 1;
        long num = n;
        if(n < 0) 
        {
            num = -num;//如果n是负数,那么取相反数
            x = 1/x;//x取倒数
        }
        double res = 1;//存结果
        while(num)
        {
            if(num%2)
                res*=x;//如果二进制的最后一位是1,那么结果乘上目前这个基数
            x *= x;//不管咋样,num在右移,x每一轮都得变成自身的平方
            num >>= 1;
        }
        return res;
    }
};

part II 矩阵快速幂

其实和整数快速幂是一样的,只不过把乘法运算换成了矩阵乘法运算。
在这里写个简单的快速求 2 ∗ 2 2*2 22矩阵的 n n n次方例子。

#include<iostream>
#include<vector>
using namespace std;
vector<vector<int>> fun(vector<vector<int>> M,vector<vector<int>> N)
{
	//这个意思是实现两个方阵的矩阵乘积
	int x = M.size();
	vector<vector<int>> res(2,vector<int>(2));
	for(int i = 0;i<x;i++)
	{
		for(int j = 0;j<x;j++)
		{
			for(int k = 0;k < x;k++)
				res[i][j] += M[i][k]*N[k][j];
		}
	}
	return res;
}

vector<vector<int>> quickMa(vector<vector<int>> M,int n)
{
	vector<vector<int>>e = {{1,0},{0,1}};
	//下面开始和整数快速幂一样的操作
	while(n)
	{
		if(n%2)
			e = fun(e,M);
		M = fun(M,M);
		n >>= 1;
	}
	return e;
}


int main()
{
	//定义一个二维数组
	vector<vector<int>>a = {{1,2},{3,4}};
	//求平方
	vector<vector<int>>ans = quickMa(a,2);
	for(int i = 0;i<2;i++)
	{
		for(int j = 0;j<2;j++)
			cout<<ans[i][j]<<endl;
	}
	return 0;
}

矩阵快速幂就多了一个写矩阵相乘的子函数,一定要记住快速幂的套路!

part III 快速斐波那契

力扣509.斐波那契数为例

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。

示例 1:
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:
输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:
输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
 
提示:
0 <= n <= 30
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fibonacci-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析:
其实这个写个递归写个迭代,都可以过,大可不必花这么多功夫写矩阵快速幂。所以仅仅是借题目重温知识点。
迭代做法:

class Solution {
public:
    int fib(int n) {
        if(!n )return 0;
        if(n==1)return 1;
        int a = 0,b = 1;
        for(int i = 2;i<=n;i++)
        {
            int temp = a+b;
            a = b;
            b = temp;        
        }
        return b;
    }
};

下面使用矩阵快速幂来做:
假设一个矩阵
A = [ 1 1 1 0 ] A=[\begin {matrix} 1&1 \\ 1 & 0 \\ \end {matrix}] A=[1110]

[ f ( 2 ) f ( 1 ) 0 0 ] [\begin {matrix} f(2)&f(1) \\ 0 & 0 \\ \end {matrix}] [f(2)0f(1)0] ∗ * [ 1 1 1 0 ] [\begin {matrix} 1&1 \\ 1 & 0 \\ \end {matrix}] [1110] = = = [ f ( 3 ) f ( 2 ) 0 0 ] [\begin {matrix} f(3)&f(2) \\ 0 & 0 \\ \end {matrix}] [f(3)0f(2)0]
如此迭代,第n个斐波那契数便是
[ f ( 2 ) f ( 1 ) 0 0 ] [\begin {matrix} f(2)&f(1) \\ 0 & 0 \\ \end {matrix}] [f(2)0f(1)0] ∗ * [ 1 1 1 0 ] ( n − 2 ) [\begin {matrix} 1&1 \\ 1 & 0 \\ \end {matrix}]^{(n-2)} [1110](n2) = = = [ f ( n ) f ( n − 1 ) 0 0 ] [\begin {matrix} f(n)&f(n-1) \\ 0 & 0 \\ \end {matrix}] [f(n)0f(n1)0]

做完矩阵快速幂之后取[0][0]位置的数即可得到第n个斐波那契。
所以先对
[ 1 1 1 0 ] [\begin {matrix} 1&1 \\ 1 & 0 \\ \end {matrix}] [1110]
( n − 2 ) (n-2) (n2)次幂,再去做一下矩阵相乘即可。

class Solution {
public:
    vector<vector<int>> fun(vector<vector<int>> M,vector<vector<int>> N)
    {
        //这个意思是实现两个方阵的矩阵乘积
        int x = M.size();
        vector<vector<int>> res(2,vector<int>(2));
        for(int i = 0;i<x;i++)
        {
            for(int j = 0;j<x;j++)
            {
                for(int k = 0;k < x;k++)
                    res[i][j] += M[i][k]*N[k][j];
            }
        }
        return res;
    }
    vector<vector<int>> quickMa(vector<vector<int>> M,int n)
    {
        //求M矩阵的n次幂
        vector<vector<int>>e = {{1,0},{0,1}};
        //下面开始和整数快速幂一样的操作
        while(n)
        {
            if(n%2)
                e = fun(e,M);
            M = fun(M,M);
            n >>= 1;
        }
        return e;
    }
    int fib(int n) {
        if(!n) return 0;
        if(n == 1 || n == 2)return 1;
        vector<vector<int>>a = {{1,1},{0,0}};
	    vector<vector<int>>b = {{1,1},{1,0}};
        //求b矩阵的n-2次幂
        vector<vector<int>>res1 = quickMa(b,n-2);
        //俩矩阵相乘
        vector<vector<int>>res2 = fun(a,res1);
        return res2[0][0];
    }
};

这个做法开了很多的空间,只有在遇到极大极大的数 的时候才会体现出优势。

总结

整数快速幂、矩阵快速幂、斐波那契数的矩阵求法复习了一下。
如有不当之处,欢迎批评指正~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值