「剑指 Offer 10-Ⅰ.斐波那契数列」

335 阅读3分钟

「剑指 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] = 0dp[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))

链接

斐波那契数列