【刷穿 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 原题链接和其他优选题解。

相关文章
|
6月前
|
算法 Go
【LeetCode 热题100】73:矩阵置零(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 73——矩阵置零问题,提供两种解法:一是使用额外标记数组,时间复杂度为 O(m * n),空间复杂度为 O(m + n);二是优化后的原地标记方法,利用矩阵的第一行和第一列记录需要置零的信息,将空间复杂度降低到 O(1)。文章通过清晰的代码示例与复杂度分析,帮助理解“原地操作”及空间优化技巧,并推荐相关练习题以巩固矩阵操作能力。适合刷题提升算法思维!
152 9
|
6月前
|
机器学习/深度学习 算法 Go
【LeetCode 热题100】139:单词拆分(动态规划全解析+细节陷阱)(Go语言版)
本题是 LeetCode 热题 139:单词拆分(Word Break),需判断字符串 `s` 是否能由字典 `wordDict` 中的单词拼接而成。通过动态规划(DP)或记忆化搜索解决。DP 中定义布尔数组 `dp[i]` 表示前 `i` 个字符是否可拆分,状态转移方程为:若存在 `j` 使 `dp[j]=true` 且 `s[j:i]` 在字典中,则 `dp[i]=true`。初始条件 `dp[0]=true`。代码实现中用哈希集合优化查找效率。记忆化搜索则从起始位置递归尝试所有切割点。两种方法各有利弊,DP 更适合面试场景。思考扩展包括输出所有拆分方式及使用 Trie 优化大字典查找。
156 6
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
143 6
|
存储 算法 NoSQL
LeetCode第73题矩阵置零
文章介绍了LeetCode第73题"矩阵置零"的解法,通过使用矩阵的第一行和第一列作为标记来记录哪些行或列需要置零,从而在不增加额外空间的情况下解决问题。
LeetCode第73题矩阵置零
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
149 4
|
12月前
|
算法 C++
Leetcode第59题(螺旋矩阵2)
这篇文章介绍了解决LeetCode第59题“螺旋矩阵II”的算法,通过C++编程实现按顺时针顺序填充一个n x n的正方形矩阵。
74 0
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
152 6
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
111 4
|
算法 Python
【Leetcode刷题Python】剑指 Offer 33. 二叉搜索树的后序遍历序列
本文提供了一种Python算法,用以判断给定整数数组是否为某二叉搜索树的后序遍历结果,通过识别根节点并递归验证左右子树的值是否满足二叉搜索树的性质。
87 3
|
Python
【Leetcode刷题Python】剑指 Offer 32 - II. 从上到下打印二叉树 II
本文提供了一种Python实现方法,用于层次遍历二叉树并按层打印结果,每层节点按从左到右的顺序排列,每层打印到一行。
115 3

热门文章

最新文章