1.题目
题目地址(LCR 126. 斐波那契数 - 力扣(LeetCode))
https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/
题目描述
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
答案需要取模 1e9+7(1000000007) ,如计算初始结果为:1000000008,请返回 1。
示例 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 <= 100
2.题解
首先注意下, C++中取模运算%, 要求计算两边均是整数类型, 如果是float,double类型, 就会报错!!!!
这里已经给出了转移方程, 我们直接使用递归或者递推实现动态规划即可
2.1 递归实现动态规划
思路
斐波那契数列,经典递归题目,但是这里会出现超出时间限制的问题.如下图,会存在大量重复计算!!!
所以我们采用记忆化搜索的问题进行操作简化!
所谓记忆化搜索,就是将已经计算过的结果进行存储,如果已经计算过了,就不再次进行递归计算了.
比如像 fib(6) = fib(5) + fib(4), fib(5)递归计算出了fib(4),....; 后面那个fib(4)就不需要计算了
注意C++中类内是不能初始化静态数组的,必须在类外进行初始化,所以我们要不然在类外定义数组,要不然将数组的初始化放在类外!!!!
这一点跟Java不一样,一定要注意.
代码
- 语言支持:C++
C++ Code:
(TTL,超出时间限制)TTL代码如下:
class Solution {
public:
int fib(int n) {
if (n < 2) return n;
return (fib(n-1) + fib(n-2)) % static_cast<int>(1e9+7);
}
};
优化后代码如下:
int cache[110];
class Solution {
public:
int fib(int n) {
if(n < 2) return n;
if(cache[n] != 0) return cache[n];
cache[n] = (fib(n-1) + fib(n-2)) % static_cast<int>(1e9+7);
return cache[n];
}
};
Java 可以实现类内静态数组初始化
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];
}
}
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(n)\)
2.2 递推实现动态规划
思路
这里由于我只需要最终结果,所以只需要存储三个元素即可,将空间复杂度简化到O(1)
但是注意这里计算时next = (prev + cur) % static_cast
有着 0 <= n <= 100, 而其实计算到46附近时, int类型就溢出了, 所以这里使用了一个取模运算
代码
class Solution {
public:
int fib(int n) {
int prev = 0, cur = 1, next = 1;
if(n <= 1) return n;
for(int i = 2; i <= n; i++){
int temp = next;
next = (prev + cur) % static_cast<int>(1e9+7);
temp = cur;
cur = next;
prev = temp;
}
return next;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(1)\)
2.3 打表
思路
n较小, 我们可以直接使用方法二中的思路现将所有cache[n]初始化, 之后直接要哪个调用哪个即可.
这里有一个很难受的问题, 就是C++中并没有像是Java中的那种静态代码块, 不然打表初始化使用static{}就能解决
这里只能定义一个静态初始化函数, 里面存储静态数组和静态flag标志, 除了第一次进行打表操作外, 其余时刻initialized=true, 直接返回结果!
这里函数定义了静态变量, 也就必须是static类型!!!
代码
class Solution {
public:
static const int mod = 1000000007;
// 静态成员函数 cache(), 这个函数返回一个指向静态数组的指针
static int* init() {
static int cache[110];
static bool initialized = false; // 使用一个静态flag标志, 决定是否进行初始化
if (!initialized) {
initialized = true;
cache[0] = 0;
cache[1] = 1;
const int mod = 1000000007;
for (int i = 2; i < 110; i++) {
cache[i] = (cache[i - 1] + cache[i - 2]) % mod;
}
}
return cache;
}
int fib(int n) {
return init()[n];
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(1)\)
2.4 矩阵快速幂
思路
将这里面的递归运算转换为重复的矩阵幂运算,然后使用矩阵快速幂定义解决问题
见矩阵快速幂定义
代码
using ll = long long;
const int m = 2;
const int MOD = 1e9 + 7;
class Matrix {
public:
// 定义一个二维数组vector
vector<vector<int>> a;
Matrix() {
// 初始化二维数组vector为m*m的零矩阵
a.assign(m, vector<int>(m, 0));
}
Matrix(vector<vector<int>>& b) {
a.assign(b.begin(), b.end());
}
void init() {
a[0][0] = a[1][1] = 1;
a[0][1] = a[1][0] = 0;
}
/**
Matrix operator*(const Matrix& other) const {
Matrix ans;
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < m; k++) {
ans.a[i][j] += (ll)a[i][k] * other.a[k][j] % MOD; // 这里前面的乘法运算就会超出int范围,所以强制转换ll类型; 为何还要%MOD呢?因为int存不下,会进行截取,所以我们只能先%MOD
}
ans.a[i][j] %= MOD;
}
}
return ans;
}
**/
// 针对这一题可以直接进行简化, 因为知道必定是2*2矩阵, 所以一个MOD即可!
Matrix operator*(const Matrix& other) const {
Matrix ans;
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
ans.a[i][j] = (a[i][0] * other.a[0][j] + a[i][1] * other.a[1][j]) % MOD;
}
}
return ans;
}
Matrix operator^(int n) const {
Matrix ans;
ans.init();
Matrix A = *this; // 复制一份用于自乘
while (n) {
if (n & 1) {
ans = ans * A;
}
A = A * A;
n >>= 1;
}
return ans;
}
};
class Solution {
public:
int fib(int n) {
if (n < 2) return n;
vector<vector<int>> b = {{1, 1}, {1, 0}};
Matrix ma(b);
ma = ma ^ (n - 1);
return ma.a[0][0]; // 这里给的幂是n-1,F(n)在上面,结果实际上是 a[0][0] * F(1) + a[0][1] * F(0) 但是 F[1] = 1, F[0] = 0;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(logn)\)
- 空间复杂度:\(O(1)\)