剑指 Offer 每日一题 | 7、斐波那契数列

550 阅读3分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

一、前言

大家好,本文章属于《剑指 Offer 每日一题》中的系列文章中的第 7 篇。

在该系列文章中我将通过刷题练手的方式来回顾一下数据结构与算法基础,同时也会通过博客的形式来分享自己的刷题历程。如果你刚好也有刷算法题的打算,可以互相鼓励学习。我的算法基础薄弱,希望通过这两三个月内的时间弥补这块的漏洞。本次使用的刷题语言为 Java ,预计后期刷第二遍的时候,会采用 Python 来完成。

二、题目

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

三、解题

3.1 思路1:递归

斐波那契数列在刚接触算法题的时候还是有所耳闻的,甚至刚看到这题的时候发现是送分题,但是还得考虑更好的算法来解决这个问题才是关键。不过为了入门方便,我们还是先把这基本的给掌握了。

我们在教科书上经常看到他们拿这个例子来解析递归函数,但是这题事实上并不适合这种解法,我们看下面的这张图,以f(10)为例来分析这个求解过程,需要找到f(9)和f(8),可以用树形结构来表示这种依赖关系。

img

基于递归求解的效果,可以发现很多节点都大量重复了,而且重复的节点数会随着n的增大而急剧增大,因此,这种解法是面试官不期待的。递归的原理就是拆分为f(n)=f(n-1)+f(n-2),然后以f(0)和f(1)做出口

3.1.1 代码
    // 1、递归
      class Solution{
         public int f(int n){
             if(n>1){
                 return f(n-1)+f(n-2);
             }else if(n==1){
                 return 1;
             }else{
                 return 0;
             }
         }
     }
3.1.2 运行效果

很遗憾,这个例子没能通过leetcode 的测试。

image-20210810213624350

3.2 思路2:动态规划

动态规划的问题往往比较有难度,而这题属于动态规划的入门题目,不过,后面有时间再根据专题来刷题。

动态规划的题目往往分几步走:

  • 1、找到“状态“和“选择”
  • 2、明确dp数组函数定义
  • 3、寻找状态之间的关系

我们可以把这个数字先计算出来,找找规律。

对应的关系如下:

n01234567
f(n)011235813

即f(n)=f(n-1)+f(n-2) 的数字呈现。

然后,我们其实就可以用代码的方式把这种值往后循环计算出来即可,只需要两个变量来记录两个值交替前进,然后用辅助变量做累加即可。

3.2.1 代码
     // 2、动态规划
     static class Solution2{
         public int f(int n){
             if(n>1){
                 // 原地动态规划,不占用额外空间 f(n+1)=f(n)+f(n-1)
                 int left=0;
                 int right =1;
                 for (int i = 0; i <n; i++) {
                     int tmp =(left+right)%1000000007;
                     left=right;
                     right=tmp;
                 }
                 return left;
             }else if(n==1){
                 return 1;
             }else{
                 return 0;
             }
         }
     }
3.2.2 执行效果

image-20210810215122999

3.2.3 复杂度
  • 时间复杂度O(n):计算f(n)需要循环n次
  • 空间复杂度O(1):几个变量使用额外空间O(1)

四、总结

在这题可以发现,用不同的方法求解斐波那契数列的时间效率大不同。第一种基于递归的解法虽然直观但时间效率很低,在实际开发种更不会采纳。第二种动态规划大大提升了时间效率,是非常不错的解法,这两种都必须掌握。

本题考察了对递归和循环的理解。

今天的刷题就到这了,欢迎点赞评论交流,剑指 Offer 刷题之旅将继续展开!