【刷穿 LeetCode】剑指 Offer 10- I. 斐波那契数列 :「动态规划」&「打表」&「矩阵快速幂」

简介: 【刷穿 LeetCode】剑指 Offer 10- I. 斐波那契数列 :「动态规划」&「打表」&「矩阵快速幂」

网络异常,图片无法展示
|

题目描述



这是 LeetCode 上的 剑指 Offer 10- I. 斐波那契数列 ,难度为 简单


Tag : 「动态规划」、「线性 DP」、「记忆化搜索」、「打表」、「矩阵快速幂」


写一个函数,输入 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。


示例 1:


输入:n = 2
输出:1
复制代码


示例 2:


输入:n = 5
输出:5
复制代码


提示:


  • 0 <= n <= 100


递推实现动态规划



既然转移方程都给出了,直接根据转移方程从头到尾递递推一遍即可。


代码:


class Solution {
    int mod = (int)1e9+7;
    public int fib(int n) {
        if (n <= 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int c = a + b;
            c %= mod;
            a = b;
            b = c;
        }
        return b;
    }
}
复制代码


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


递归实现动态规划



能以「递推」形式实现动态规划,自然也能以「递归」的形式实现。


为防止重复计算,我们需要加入「记忆化搜索」功能,同时利用某个值 xx 在不同的样例之间可能会作为“中间结果”被重复计算,并且计算结果 fib(x)fib(x) 固定,我们可以使用 static 修饰缓存器,以实现计算过的结果在所有测试样例中共享。


代码:


class Solution {
    static int mod = (int)1e9+7;
    static int N = 110;
    static int[] cache = new int[N];
    public int fib(int n) {
        if (n <= 1) return n;
        if (cache[n] != 0) return cache[n];
        cache[n] = fib(n - 1) + fib(n - 2);
        cache[n] %= mod;
        return cache[n];
    }
}
复制代码


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


打表



经过「解法二」,我们进一步发现,可以利用数据范围只有 100100 进行打表预处理,然后直接返回。


代码:


class Solution {
    static int mod = (int)1e9+7;
    static int N = 110;
    static int[] cache = new int[N];
    static {
        cache[1] = 1;
        for (int i = 2; i < N; i++) {
            cache[i] = cache[i - 1] + cache[i - 2];
            cache[i] %= mod;
        }
    }
    public int fib(int n) {
        return cache[n];
    }
}
复制代码


  • 时间复杂度:将打表逻辑放到本地执行,复杂度为 O(1)O(1);否则为 O(C)O(C)CC 为常量,固定为 110110
  • 空间复杂度:O(C)O(C)


矩阵快速幂



对于数列递推问题,可以使用矩阵快速幂进行加速,最完整的介绍在 这里 讲过。


对于本题,某个 f(n)f(n) 依赖于 f(n - 1)f(n1)f(n - 2)f(n2),将其依赖的状态存成列向量:


\begin{bmatrix} f(n - 1)\\ f(n - 2) \end{bmatrix}[f(n1)f(n2)]


目标值 f(n)f(n) 所在矩阵为:


\begin{bmatrix} f(n)\\ f(n - 1) \end{bmatrix}[f(n)f(n1)]


根据矩阵乘法,不难发现:


\begin{bmatrix} f(n)\\ f(n - 1) \end{bmatrix} = \begin{bmatrix} 1& 1\\ 1& 0 \end{bmatrix} * \begin{bmatrix} f(n - 1)\\ f(n - 2) \end{bmatrix}[f(n)f(n1)]=[1110][f(n1)f(n2)]


我们令:


mat = \begin{bmatrix} 1& 1\\ 1& 0 \end{bmatrix}mat=[1110]


起始时,我们只有 \begin{bmatrix} f(1)\\ f(0) \end{bmatrix}[f(1)f(0)],根据递推式得:


\begin{bmatrix} f(n)\\ f(n - 1) \end{bmatrix} = mat * mat * ... * mat * \begin{bmatrix} f(1)\\ f(0) \end{bmatrix}[f(n)f(n1)]=matmat...mat[f(1)f(0)]


再根据矩阵乘法具有「结合律」,最终可得:


\begin{bmatrix} f(n)\\ f(n - 1) \end{bmatrix} = mat^{n - 1} * \begin{bmatrix} f(1)\\ f(0) \end{bmatrix}[f(n)f(n1)]=matn1[f(1)f(0)]


计算 mat^{n - 1}matn1 可以套用「快速幂」进行求解。


代码:


class Solution {
    int mod = (int)1e9+7;
    long[][] mul(long[][] a, long[][] b) {
        int r = a.length, c = b[0].length, z = b.length;
        long[][] ans = new long[r][c];
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                for (int k = 0; k < z; k++) {
                    ans[i][j] += a[i][k] * b[k][j];
                    ans[i][j] %= mod;
                }
            }
        }
        return ans;
    }
    public int fib(int n) {
        if (n <= 1) return n;
        long[][] mat = new long[][]{
            {1, 1},
            {1, 0}
        };
        long[][] ans = new long[][]{
            {1},
            {0}
        };
        int x = n - 1;
        while (x != 0) {
            if ((x & 1) != 0) ans = mul(mat, ans);
            mat = mul(mat, mat);
            x >>= 1;
        }
        return (int)(ans[0][0] % mod);
    }
}
复制代码


  • 时间复杂度:O(\log{n})O(logn)
  • 空间复杂度:O(1)O(1)


其他「打表」内容



题目 题解 难度 推荐指数
401. 二进制手表 LeetCode 题解链接 简单 🤩🤩🤩🤩🤩
1137. 第 N 个泰波那契数 LeetCode 题解链接 简单 🤩🤩🤩🤩
1646. 获取生成数组中的最大值 LeetCode 题解链接 简单 🤩🤩🤩🤩
面试题 10.02. 变位词组 LeetCode 题解链接 中等 🤩🤩🤩🤩


注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。


其他「矩阵快速幂」内容



题目 题解 难度 推荐指数
552. 学生出勤记录 II LeetCode 题解链接 困难 🤩🤩🤩🤩
1137. 第 N 个泰波那契数 LeetCode 题解链接 简单 🤩🤩🤩🤩🤩


注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。


最后



这是我们「刷穿 LeetCode」系列文章的第 剑指 Offer 10- I 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
4月前
|
存储 算法 NoSQL
LeetCode第73题矩阵置零
文章介绍了LeetCode第73题"矩阵置零"的解法,通过使用矩阵的第一行和第一列作为标记来记录哪些行或列需要置零,从而在不增加额外空间的情况下解决问题。
LeetCode第73题矩阵置零
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
63 6
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
57 4
|
2月前
|
算法 C++
Leetcode第59题(螺旋矩阵2)
这篇文章介绍了解决LeetCode第59题“螺旋矩阵II”的算法,通过C++编程实现按顺时针顺序填充一个n x n的正方形矩阵。
18 0
|
4月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
57 6
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
58 5
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
34 4
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode题目"剑指 Offer 21. 调整数组顺序使奇数位于偶数前面"的两种Python解决方案,一种是使用双端队列调整数组顺序,另一种是使用双指针法将奇数移到数组前半部分,偶数移到后半部分。
29 4
|
4月前
|
算法 Python
【Leetcode刷题Python】剑指 Offer 33. 二叉搜索树的后序遍历序列
本文提供了一种Python算法,用以判断给定整数数组是否为某二叉搜索树的后序遍历结果,通过识别根节点并递归验证左右子树的值是否满足二叉搜索树的性质。
27 3
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - II. 从上到下打印二叉树 II
本文提供了一种Python实现方法,用于层次遍历二叉树并按层打印结果,每层节点按从左到右的顺序排列,每层打印到一行。
42 3