• LeetCode 0509. 斐波那契数:尝试以四种方式吃透(四种大方法+两种小优化)


    【LetMeFly】尝试以四种方式吃透:509.斐波那契数(四种大方法+两种小优化)

    先说明本题解法:

    1. 动态规划(及 原地滚动的优化)
    2. 递归(及 记忆化的优化)
    3. 矩阵快速幂
    4. 通项公式

    力扣题目链接:https://leetcode.cn/problems/fibonacci-number/

    斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

    F(0) = 0,F(1) = 1
    F(n) = F(n - 1) + F(n - 2),其中 n > 1
    

    给定 n ,请计算 F(n)

     

    示例 1:

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

    示例 2:

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

    示例 3:

    输入:n = 4
    输出:3
    解释:F(4) = F(3) + F(2) = 2 + 1 = 3
    

     

    提示:

    • 0 <= n <= 30

    方法一:动态规划

    开辟一个大小为 30 30 30的数组 a a a(或开辟大小为 n + 1 n+1 n+1的数组也可)

    初始值 a [ 0 ] = 0 , a [ 1 ] = 1 a[0] = 0, a[1] = 1 a[0]=0,a[1]=1

    之后从下标 2 2 2开始到 n n n为止,使用转移方程 a [ n ] = a [ n − 1 ] + a [ n − 2 ] a[n] = a[n - 1] + a[n - 2] a[n]=a[n1]+a[n2]求解

    最终返回a[n]即可

    • 时间复杂度 O ( n ) O(n) O(n)
    • 空间复杂度 O ( C ) O(C) O(C),这里 C C C是数据中 n n n的最大值 30 30 30(也可以只开辟 n + 1 n+1 n+1的数组,则空间复杂度为 O ( n ) O(n) O(n)

    AC代码

    C++
    class Solution {  // 开辟一整个数组
    public:
        int fib(int n) {
            int a[31] = {0, 1};
            for (int i = 2; i <= n; i++)
                a[i] = a[i - 1] + a[i - 2];
            return a[n];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方法一.2:动态规划 + 原地滚动

    不难发现,在计算 a [ n ] a[n] a[n]时,我们只用到了 a [ n − 1 ] a[n-1] a[n1] a [ n − 2 ] a[n-2] a[n2],再往前的数据就再也用不到了

    因此,我们只需要使用两个额外的空间 0 _0 0 1 _1 1来分别记录 a [ n − 1 ] a[n-1] a[n1]和a[n-2] 的值,在计算过程中,不断更新 的值,在计算过程中,不断更新 的值,在计算过程中,不断更新_0 和 和 _1$的值即可

    • 时间复杂度 O ( n ) O(n) O(n)
    • 空间复杂度 O ( 1 ) O(1) O(1)

    AC代码

    C++
    class Solution {  // 两个额外变量模拟
    public:
        int fib(int n) {
            if (n < 2)
                return n;
            int _0 = 0, _1 = 1;  // 分别代表a[n - 2]和a[n - 1]
            int ans;
            for (int i = 2; i <= n; i++) {
                ans = _0 + _1;
                _0 = _1, _1 = ans;  // Update
            }
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    方法二:递归

    斐波那契数列很容易看出“递归”

    题目都说明了, F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n - 1) + F(n - 2) F(n)=F(n1)+F(n2),终止条件是 n = 0 n = 0 n=0 n = 1 n = 1 n=1

    那么,我们只需要在非终止条件下无脑递归即可。

    • 时间复杂度 O ( n 2 ) O(n^2) O(n2)这个时间复杂度待会儿分析
    • 空间复杂度 O ( n ) O(n) O(n),不论总递归次数为多少,总递归深度为 n n n

    AC代码

    C++
    class Solution {  // 递归
    public:
        int fib(int n) {
            if (n < 2)
                return n;
            return fib(n - 1) + fib(n - 2);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    方法二.2:递归 + 记忆化

    方法二在数据量小的前提下能很轻松地计算出结果。

    但是为什么方法二的时间复杂度是 O ( n 2 ) O(n^2) O(n2)呢?

    不难发现,计算 F ( 5 ) F(5) F(5)时,会调用 F ( 4 ) F(4) F(4) F ( 3 ) F(3) F(3),但在计算 F ( 4 ) F(4) F(4)时,会再调用一次 F ( 3 ) F(3) F(3),也就是说 F ( 3 ) F(3) F(3)不只被调用了一次

    例如计算 F ( 6 ) F(6) F(6)时:

    demo

    F ( 4 ) F(4) F(4)计算了两次, F ( 3 ) F(3) F(3)计算了三次, F ( 2 ) F(2) F(2)更是计算了五次。

    n n n越大,这种重复计算就越明显。

    那么,既然算过一遍 F ( 3 ) F(3) F(3)了,为什么要再算一次呢?

    记忆化出现了

    我们使用一个哈希表,将计算过的结果记录下来,那么再次调用这个函数时,直接返回之前计算过的结果不就可以了吗?

    demo2

    这样就避免了没必要的重复计算。

    • 时间复杂度 O ( n ) O(n) O(n)
    • 空间复杂度 O ( n ) O(n) O(n)

    AC代码

    C++
    class Solution {  // 递归 + 记忆化
    private:
        unordered_map<int, int> ma;
    public:
        int fib(int n) {
            if (n < 2)
                return n;
            if (ma.count(n))
                return ma[n];
            return ma[n] = fib(n - 1) + fib(n - 2);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方法三:矩阵快速幂

    [ 1 1 1 0 ] [ a n a n − 1 ] = [ a n + a n − 1 a n ] = [ a n + 1 a n ] \left[1110\right]\left[anan1\right]=\left[an+an1an\right]=\left[an+1an\right] [1110][anan1]=[an+an1an]=[an+1an]

    因此

    [ a n + 1 a n ] = [ 1 1 1 0 ] n [ a 1 a 0 ] \left[an+1an\right]=\left[1110\right]^{n}\left[a1a0\right] [an+1an]=[1110]n[a1a0]

    因此可以使用矩阵快速幂求出

    [ 1 1 1 0 ] n − 1 \left[1110\right]^{n-1} [1110]n1

    再将其右乘 [ a 1 a 0 ] \left[a1a0\right] [a1a0]即得到 [ a n a n − 1 ] \left[anan1\right] [anan1]

    假设:

    [ 1 1 1 0 ] n − 1 = [ x y m n ] \left[1110\right]^{n-1}=\left[xymn\right] [1110]n1=[xmyn]

    那么:

    [ a n a n − 1 ] = [ 1 1 1 0 ] n − 1 [ a 1 a 0 ] = [ x y m n ] [ 1 0 ] = [ x m ] \left[anan1\right]=\left[1110\right]^{n-1}\left[a1a0\right]=\left[xymn\right]\left[10\right]=\left[xm\right] [anan1]=[1110]n1[a1a0]=[xmyn][10]=[xm]

    因此 a n = x a_n=x an=x,也就是 [ 1 1 1 0 ] n − 1 \left[1110\right]^{n-1} [1110]n1的左上角的值

    • 时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)
    • 空间复杂度 O ( 1 ) O(1) O(1)

    AC代码

    C++
    struct Matrix {
        int a[2][2];
    
        Matrix(int x, int y, int m, int n) {
            a[0][0] = x, a[0][1] = y;
            a[1][0] = m, a[1][1] = n;
        }
    
        Matrix() {
            a[0][0] = 0, a[0][1] = 0;
            a[1][0] = 0, a[1][1] = 0;
        }
    };
    
    Matrix operator* (const Matrix& a, const Matrix& b) {
        Matrix result;
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                result.a[i][j] = a.a[i][0] * b.a[0][j] + a.a[i][1] * b.a[1][j];
            }
        }
        return result;
    }
    
    class Solution {
    private:
        Matrix Pow(Matrix a, int n) {  // 这里只接受>0的n
            Matrix result(1, 0, 0, 1);
            while (n) {
                if (n & 1)
                    result = result * a;
                n >>= 1;
                a = a * a;
            }
            return result;
        }
    public:
        int fib(int n) {
            if (n < 2)
                return n;
            return Pow(Matrix(1, 1, 1, 0), n - 1).a[0][0];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    方法四:通项公式

    使用生成函数求斐波那契的通项公式

         F ( x ) = F 1 x + F 2 x 2 + F 3 x 3 + F 4 x 4 + ⋯ \ \ \ \ F(x)=F_1x+F_2x^2+F_3x^3+F_4x^4+\cdots     F(x)=F1x+F2x2+F3x3+F4x4+

       x F ( x ) =             F 1 x 2 + F 2 x 3 + F 3 x 4 + ⋯ \ \ xF(x)=\ \ \ \ \ \ \ \ \ \ \ F_1x^2+F_2x^3+F_3x^4+\cdots   xF(x)=           F1x2+F2x3+F3x4+

    x 2 F ( x ) =                          F 1 x 3 + F 2 x 4 + ⋯ x^2F(x)=\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ F_1x^3+F_2x^4+\cdots x2F(x)=                        F1x3+F2x4+

    因此, ( x 2 + x ) F ( x ) = F 1 x 2 + ( F 2 + F 1 ) x 3 + ( F 3 + F 4 ) x 4 + ⋯ = F 1 x 2 + F 3 x 3 + F 4 x 4 + ⋯ (x^2+x)F(x)=F_1x^2+(F_2+F_1)x^3+(F_3+F_4)x^4+\cdots=F_1x^2+F_3x^3+F_4x^4+\cdots (x2+x)F(x)=F1x2+(F2+F1)x3+(F3+F4)x4+=F1x2+F3x3+F4x4+

    因此, ( 1 − ( x 2 + x ) ) F ( x ) = F 1 x + ( F 2 − F 1 ) x 2 (1-(x^2+x))F(x)=F_1x+(F_2-F_1)x^2 (1(x2+x))F(x)=F1x+(F2F1)x2

    ( 1 − x − x 2 ) F ( x ) = x (1-x-x^2)F(x)=x (1xx2)F(x)=x

    所以 F ( x ) = x 1 − x − x 2 = − x ( x − 5 − 1 2 ) ( x + 5 + 1 2 ) = − 1 5 ( 1 5 − 1 2 x + 1 + 1 5 + 1 2 x − 1 ) F(x)=\frac{x}{1-x-x^2}=-\frac{x}{\left(x-\frac{\sqrt{5}-1}{2}\right)\left(x+\frac{\sqrt{5}+1}{2}\right)}=-\frac{1}{\sqrt{5}}(\frac{1}{\frac{\sqrt{5}-1}{2} x+1}+\frac{1}{\frac{\sqrt{5}+1}{2} x-1}) F(x)=1xx2x=(x25 1)(x+25 +1)x=5 1(25 1x+11+25 +1x11)

    我们求出了斐波那契数列的生成函数

    接下来通过生成函数求原始数列

    G ( x ) = 1 a x + b G(x)=\frac{1}{ax+b} G(x)=ax+b1,其对应的原始数列是 { g i } \{g_i\} {gi}

    则有 1 a x + b = ∑ i = 1 ∞ g i x i \frac{1}{ax+b}=\sum_{i=1}^\infty g_ix^i ax+b1=i=1gixi

    已知 n → ∞ n\to \infty n 1 + x + x 2 + ⋯ + x n = 1 1 − x 1+x+x^2+\cdots+x^n=\frac{1}{1-x} 1+x+x2++xn=1x1

    − x -x x替换 x x x 1 1 + x = 1 − x + x 2 − x 3 + ⋯ \frac{1}{1+x}=1-x+x^2-x^3+\cdots 1+x1=1x+x2x3+

    所以 1 b x + b = 1 b − 1 b x + 1 b x 2 − 1 b x 3 + ⋯ \frac{1}{bx+b}=\frac1b-\frac1bx+\frac1bx^2-\frac1bx^3+\cdots bx+b1=b1b1x+b1x2b1x3+

    a b x \frac{a}bx bax替换 x x x 1 a x + b = 1 b − a b 2 x + a 2 b 3 x 2 − a 3 b 4 x 3 + ⋯ \frac1{ax+b}=\frac{1}{b}-\frac{a}{b^{2}} x+\frac{a^{2}}{b^{3}} x^{2}-\frac{a^{3}}{b^{4}} x^{3}+\cdots ax+b1=b1b2ax+b3a2x2b4a3x3+

    所以 x n x^n xn的系数 g n = ( − 1 ) n a n b − ( n + 1 ) g_n=(-1)^na^nb^{-(n+1)} gn=(1)nanb(n+1)

    之前我们求出 F ( x ) = − 1 5 ( 1 5 − 1 2 x + 1 + 1 5 + 1 2 x − 1 ) F(x)=-\frac{1}{\sqrt{5}}(\frac{1}{\frac{\sqrt{5}-1}{2} x+1}+\frac{1}{\frac{\sqrt{5}+1}{2} x-1}) F(x)=5 1(25 1x+11+25 +1x11)

    对于 1 5 − 1 2 x + 1 \frac{1}{\frac{\sqrt{5}-1}{2} x+1} 25 1x+11,令 a = 5 − 1 2 , b = 1 a=\frac{\sqrt{5}-1}{2},b=1 a=25 1,b=1 g n = ( 1 − 5 2 ) n g_n=(\frac{1-\sqrt5}{2})^n gn=(215 )n,因此 G ( x ) = { ( 1 − 5 2 ) x } G(x)=\{(\frac{1-\sqrt5}{2})^x\} G(x)={(215 )x}

    对于 1 5 + 1 2 x − 1 \frac{1}{\frac{\sqrt{5}+1}{2} x-1} 25 +1x11,令 a = 5 + 1 2 , b = − 1 a=\frac{\sqrt{5}+1}{2},b=-1 a=25 +1,b=1 g n = − ( 1 + 5 2 ) n g_n=-(\frac{1+\sqrt5}{2})^n gn=(21+5 )n,因此 G ( x ) = { − ( 1 + 5 2 ) x } G(x)=\{-(\frac{1+\sqrt5}{2})^x\} G(x)={(21+5 )x}

    所以 a n = − 1 5 [ ( 1 − 5 2 ) x − ( 1 + 5 2 ) x ] = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] a_n=-\frac{1}{\sqrt{5}}[(\frac{1-\sqrt5}{2})^x-(\frac{1+\sqrt5}{2})^x]=\frac{1}{\sqrt{5}}\left[\left(\frac{1+\sqrt{5}}{2}\right)^{n}-\left(\frac{1-\sqrt{5}}{2}\right)^{n}\right] an=5 1[(215 )x(21+5 )x]=5 1[(21+5 )n(215 )n] 即为所求

    • 时间复杂度不好衡量(也不能说O(1)吧,但是肯定比矩阵快速幂快,毕竟CPU有专门的求幂指令)
    • 空间复杂度 O ( 1 ) O(1) O(1)

    AC代码

    C++
    class Solution {
    public:
        int fib(int n) {
            return 1. / sqrt(5) * (pow((1 + sqrt(5)) / 2, n) - pow((1 - sqrt(5)) / 2, n));
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    有同学说,比赛的时候忘记了通项公式怎么办,总不能现场手推一遍吧

    其实我们只需要记得通项公式的大概形势: a n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] = x 1 y 1 n + x 2 y 2 n a_n=\frac{1}{\sqrt{5}}\left[\left(\frac{1+\sqrt{5}}{2}\right)^{n}-\left(\frac{1-\sqrt{5}}{2}\right)^{n}\right]=x_1y_1^n+x_2y_2^n an=5 1[(21+5 )n(215 )n]=x1y1n+x2y2n

    因此代入几个 a a a就把 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2解出来了( a 0 = 0 , a 1 = 1 , ⋯ a_0=0,a_1=1,\cdots a0=0,a1=1,

    同步发文于CSDN,原创不易,转载请附上原文链接哦~
    Tisfy:https://letmefly.blog.csdn.net/article/details/127712505

  • 相关阅读:
    java 基于微信小程序的饭店外卖点餐系统 uniapp小程序
    连锁门店进销存软件的用途
    LeetCode每日一题(2201. Count Artifacts That Can Be Extracted)
    MapReduce【MapTask和ReduceTask的工作机制】
    VMware 三种网络连接模式
    白嫖免费版gpt与wetab插件的使用
    二维纳米材料
    Qt文件读写的天花板QFile和IODevice搭配第一集
    Linux常用命令
    企业如何高效获取私域流量
  • 原文地址:https://blog.csdn.net/Tisfy/article/details/127712505