编程实现“斐波那契数列”的5种方法! | 经典面试题

编程求值斐波那契数列f(n),是面试中,非常常见的题目。
 
什么是斐波那契数列?
斐波那契数列是这样一个数列,它满足:

f(0) = 0;

f(1) = 1;

f(n) = f(n-1) + f(n-2)  (当n>=2时)

 
到底有几种方法,这些思路里蕴含的优化思路究竟是怎么样的,今天和大家聊一聊。
 
一、递归法
伪代码

uint32_t f(uint32_t n){

    if(n==0) return 0;

    if(n==1) return 1;

    return f(n-1)+f(n-2);

}

 
思路:这是一个递归的代码,非常清晰,直接把斐波那契数列的定义翻译成了代码。
 
例如
假设要求f(5)
f(5) = f(4) + f(3);
于是会递归计算f(4)和f(3);
 
接着要求f(4)
f(4) = f(3)+ f(2);
于是会递归计算f(3)和f(2);
 
可以看到,计算f(5)和f(4)中都要计算f(3),但这两次f(3)会重复计算,这就是递归的最大问题,对于同一个f(a),不能复用。
 
计算一个f(n)到底需要有多少次递归调用呢?
我们可以在代码里加一个计数验证一下。
 
伪代码

static uint32_t count=0; // 加一个全局变量计数

 

uint32_t f(uint32_t n){

    count++;  // 递归一次,计数加一

    if(n==0) return 0;

    if(n==1) return 1;

    return f(n-1)+f(n-2);

}

 
实验的结果

f(5) count = 15

f(10) count = 177

f(15) count = 1K+

f(20) count = 2W+

f(25) count = 24W+

f(30) count = 269W+

f(35) count = 2986W+

f(40) count = 3.3Y+

f(45) … 抱歉,我机器太慢,算不出来

 
额滴神哪,不是骗我吧!!!
画外音:
(1)这个count,是函数递归了对少次;
(2)f(45),机器居然算不出来;
(3)对结论有疑问的,自己可以run一把;
 
启示
(1)斐波那契数列求解,如果用直接法,时间复杂度是指数级的,不可行;
(2)如果没有太大的把握,工程中尽量少使用递归,容易把自己玩死;
 
二、正推法
从斐波那契数列的定义:

f(0) = 0;

f(1) = 1;

f(n) = f(n-1) + f(n-2) n>=2时

 
可以看出,每一个新的f(n),是前两个旧的f(n-1)和f(n-2)之和,一路递归下去,最终都将递归到f(0)和f(1)上来。
 
反过来想,我们不倒着f(n),f(n-1),f(n-2)这么计算,而是f(0),f(1),f(2)…f(n)这么正向计算,岂不是更快么?
 
伪代码

uint32_t f(uint32_t n){

   uint32_t arr[n];

   arr[0]=0;

   arr[1]=1;

   for(uint32_t i=2;i

       arr[i]=arr[i-1]+arr[i-2];

    }

   return arr[n];

}

 
这么正向的计算,只需要一个for循环,就能够计算出f(n)的值,时间复杂度是O(n)
 
三、通项公式法

f(0) = 0;

f(1) = 1;

f(n) = f(n-1) + f(n-2)  (当n>=2时)

大学学过相关课程,可解出f(n)通项公式。
画外音:额,是不是有朋友读了个假大学。
 
线性递推数列
f(n) = f(n-1) + f(n-2)
对应的特征方程是:
x^2 = x + 1
求解特征方程得到:

x1=(1+√5)/2

x2=(1-√5)/2

 
于是得到:
f(n) = a1(x1)^n + a2(x2)^n
 
将:

f(0) = 0;

f(1) = 1;

代入上述通项公式。
 
于是得到:

a1=1/√5

a2=-1/√5

 
于是最终得到

f(n)=(1/√5)*{[(1+√5)/2]^n -[(1-√5)/2]^n}

画外音:别问我为什么懂这些,我TM作为计算机信息安全专业,也被数论,有限域,加密解密这些数学学科折磨过。
 
可忽略上述吹牛*的过程,百度一下能得到答案。
编程实现“斐波那契数列”的5种方法! | 经典面试题
总之,得到了斐波那契数列通项公式:

f(n) = a1(b1)^n + a2(b2)^n

其中a1, b1, a2, b2四个数字都是常数。
 
想求f(45),把n=45带入上述通项公式即可。
 
那么,带入通项公式求解,时间复杂度是多少呢?是O(1)么?
通项公式的计算,并不能O(1)得到,而是一个a^n,即power(a, n)的求解过程。
 
那么,如何求解a的n次方呢?
最粗暴的方法,将a不断的自乘n次。
 
伪代码

uint64_t power(uint64_t a, uint64_t n){

   uint64_t result=a;

   for(uint64_t i=1;i

       result *=a;

    }

   return result;

}

很容易知道,a通过for循环不断自乘,求解a^n的时间复杂度是O(n)
 
你TM在逗我!!!
 
通过“正推”法,求解f(n)的时间复杂度是O(n)。
楼主搞了这么久的奇技淫巧,搞什么“通项公式法”,结果也是个O(n)的方法???
 
不不不,稍安勿躁,上面讲的都是思路,求解a^n,可以使用之前文章里说过的减治法
 
还记得分治法与减治法的区别么?
(1)减治法,大问题分解为小问题,小问题只要递归一个分支,例如:二分查找,随机选择;
(2)分治法,大问题分解为小问题,小问题都要迭代各个分支,例如:快速排序
具体在《一次搞透,面试中的TopK问题!》里,讲随机选择时详细介绍过;
 
a^n减治法思路
(1)当n是偶数时,先求出r=a^(n/2),再做一次r*r的计算,就得到了a^n;
(2)当n是奇数时,n-1就一定是偶数,先求出r=a^(n-1)/2,在做一次r*r*a,就得到了a^n;
 
伪代码

uint64_t power(uint64_t a, uint64_t n){

    if(n==0)return 1;

    if(n==1)return a;

 

   uint64_t r=0;

   if(n%2){

       r=power(a, (n-1)/2);

       return r*r*a;

    }

   else{

       r=power(a, n/2);

       return r*r;

    }

}

 
每次将计算规模减半,是不是和二分查找很像?减治法求a^n的时间复杂度是O(lg(n))
 
四、查表法
关键时刻,空间换时间的方法就出场了,如果有相对充裕的内存,可以有更快的算法
 
思路:f(n),一旦n的值确定,f(n)也就确定,可以提前计算好结果数组

result[0]=0;

result[1]=1;

result[2]=1;

result[3]=2;

result[4]=3;

 
求f(n)时直接打表即可,伪代码:
return result[n];
 
打表的时间复杂度是O(1)。
画外音:但次都是查表,太没有意思了。
 
五、总结
斐波那契数列,不难;
但其思路有优化过程,并不简单:
(1)递归法,f(45)能跑得死机;
(2)正推法,O(n),正推计算,有点意思;
(3)通项公式,本质转化为求a^n;
(4)减治法求a^n,O(lg(n));
(5)打表法,O(1),空间换时间;
画外音:注意,面试现场求解通项公式,有可能会吓到面试官,请慎用。
 
知其然,知其所以然。思路比结论重要。

架构师之路-分享可落地的架构文章

 
推荐阅读
世界上最美的排序算法!
世界上最慢的排序算法!
世界上最开脑洞的排序算法!
 
希望大家对“斐波那契数列”有新的认识,谢

发布者:糖太宗,转载请注明出处:https://www.qztxs.com/archives/science/technology/6012

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年5月11日 下午8:48
下一篇 2022年5月11日 下午8:49

相关推荐

  • 究竟为啥总在凌晨上线,如何进行无损发布

    为什么很多互联网公司升级系统,选择在晚上上线? 美名其曰,晚上上线,对用户影响最小。   为什么会对用户产生影响? 很多人认为,系统升级往往需要重启,重启的过程中,正在访问的用户会访问失败。   例如,如果升级的是web-server: 如上图,重启ip1上的tomcat时,tomcat上或许有1000个http请求正在处理,这些请求就会失败。   又例如,...

    2022年5月15日
    4100
  • java面试字节跳动——字节码

    1.前言 先来个定义:Java字节码是一组可以由Java虚拟机(JVM)执行的高度优化的指令,它被记录在Class文件中,在虚拟机加载Class文件时执行。 说大白话就是,字节码是Java虚拟机能够看明白的可执行指令。 前面的文章中已经强调了很多次了,Class文件不等于字节码,为什么我要一直强调这个事情呢? 因为在绝大部分的中文资料和博客中,这两个东西都被...

    2022年5月18日
    4600
  • 某鱼App加密参数x-sign生成教程

    通过对某鱼App抓包,可发现数据请求接口中都必须携带x-sign参数,而x-sign参数的生成方式不得而知,只能通过对App进行反编译破解。本教程将利用HermesAgent框架实现对x-sign生成方法的Hook及远程调用签名方法。 本教程以某鱼App v6.5.10版本为例 第一步:反编译某鱼App 利用dex2jar工具反编译某鱼APK文件,反编译具体...

    2022年5月7日
    24500
  • 嘿,技术人,你开会的时间多,还是撸码的时间多?

    作为技术人的你,是不是苦于“被低效的会议占据了绝大部分时间”,而没有连续撸码的时间? 7项开会最佳实践,帮大家高效开会,安心撸码。 实践一:邀请正确的人。 人越少越高效,请务必提前确定人员范围,而不是发给一个部门。 实践二:提前发出邀请。 谁都不喜欢临时的会议,邀请务必明确主题,以及大致的流程。 实践三:准时开始。 准时是基本的职业素养。 实践四:好的开场是...

    2022年5月12日
    1700
  • use_algolia_search

    去algolia官网注册账号 新建index索引 Blog   在hex项目下安装 1 2 3 4 5 6 7 8 9 10 # npm install hexo-algolia --save # export HEXO_ALGOLIA_INDEXING_KEY=xxxxxxxxxxxxxxxxxxxxxx # hexo algolia INFO ...

    技术 2022年6月2日
    1600

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信