「剑指 Offer 10-Ⅰ.斐波那契数列」
题目描述(level 简单)
写一个函数,输入 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
思路分析
初看题目可能会第一时间想到使用递归来解,但是会存在几个问题:1、递归的调用栈过于深(n<=100),一般电脑也不能够计算出结果,中途可能就报异常了。2、存在大量的重复计算,f(n) = f(n-1)+f(n-2),白白浪费了计算力。既然递归不行,那么只能考虑别的方法,递归法不合适的原因是存在大量的重复计算。回忆 算法导论 中,钢锯条切割的问题就类似这种 重叠子问题。可以想到使用 动态规划DP(Dynamic Programming) 来解决,而DP的关键是在于转移方程的构造。根据题意总结得出:
- 设
dp为一纬数组,则dp[n]就表表斐波那契数列中的第n个数字 - 初始状态得出为:
dp[0] = 0与dp[1] = 1 f(n) = f(n - 1) + f(n - 2)由于需要求导的为f(n)- 转移方程则为:
dp[n]=dp[n -1]+dp[n-2],返回dp[n]即是所需要求的值。
代码实现
- 根据思路可以得到如下实现
class Solution {
public int fib(int n) {
if(0 == n || 1 == n) {
return n;
}
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
dp[i] %= 1000000007;
}
return dp[n];
}
}
区别于递归的重复计算,使用动态规划避免了重叠子问题,就对于代码的实现来看,还是可以优化的,在定义的初始阶段直接规定了数组的长度int[] dp = new int[n+1];但其实没有必要将无关的结果都存入到数组当中,目标仅仅是需要得到第n项的值,只需要在推演的过程中不断更新这个值即可。
class Solution {
public int fib(int n) {
if(0 == n || 1 == n) {
return n;
}
//F(0) = 0
//F(1) = 1
//F(2) = F(2 - 1) + F(2 - 2) = F(1) + F(0) = 1
//F(3) = F(3 - 1) + F(3 - 2) = F(2) + F(1) = 2
//根据sum的值不断推进
int a = 0, b = 1, sum = a + b;
for (int i = 2; i < n; i++) {
a = b;
b = sum;
sum = (a + b) % 1000000007;
}
return sum;
}
}
复杂度
时间复杂度O(N):循环的次数N成线性关系
空间复杂度O(1):定义变量所需的内存空间为常量(如果使用数组法,空间复杂度为O(N))