面试中,问得比较多的几个问题之一,求斐波那契数列f(n)?

画外音:姐妹篇

《拜托,面试别再问我TopK了!!!》

《拜托,面试别再让我数1了!!!》

什么是斐波那契数列?

斐波那契数列是这样一个数列,它满足:

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<=n;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作为计算机信息安全专业,也被数论,有限域,加密解密这些数学学科折磨过。

可忽略上述吹牛*的过程,百度一下能得到答案。

总之,得到了斐波那契数列通项公式:

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,可以使用之前文章里说过的减治法。

还记得分治法与减治法的区别么?

减治法,大问题分解为小问题,小问题只要递归一个分支,例如:二分查找,随机选择

分治法。大问题分解为小问题,小问题都要迭代各个分支,例如:快速排序

具体在《拜托,面试别再问我TopK了!!!》里,讲随机选择时详细介绍过。

a^n减治法思路:

当n是偶数时,先求出r=a^(n/2),再做一次r*r的计算,就得到了a^n

当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)。

画外音:但每期都讲打表,太没有意思了。

五、总结

斐波那契数列,不难;

但其思路有优化过程,并不简单:

递归法,f(45)能跑得死机

正推法,O(n),正推计算,有点意思

通项公式,本质转化为求a^n

减治法求a^n,O(lg(n))

打表法,O(1),空间换时间

画外音:注意,面试现场求解通项公式,有可能会吓到面试官,请慎用。

知其然,知其所以然。

思路比结论重要。

希望大家对“斐波那契数列”有新的认识,谢转。

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

推荐阅读:

《拜托,面试别再问我TopK了!》

《拜托,面试别再让我数1了!》

c语言减治法求a的n次方算法,拜托,面试别再问我斐波那契数列了!!!相关推荐

  1. 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...

    面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...

  2. 用递归调用法求斐波那契函数_进阶版:面试官问你斐波那契数列的时候不要高兴得太早...

    增加内容 递归改进版 矩阵快速幂解法 通项表达式解法 列表法 斐波那契数列应用 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归 ...

  3. 数据结构三大查找算法(二分查找、插值查找、斐波那契数列查找)C语言实现

    文章目录 查找 二分查找(折半查找) 插值查找 斐波拉契查找 总结: 查找 查找是在大量的信息里面寻找一个特定的信息元素 (1)静态查找和动态查找: 静态或者动态都是针对查找表而言的.动态表指查找表中 ...

  4. 1,2,3,5,8,13用C语言输出前20项,c语言,编程实现,求斐波那契数列,1,1,2,3,5,8,......的前20项及前20项和...

    C语言源程序如下: #include int main() { int array[100]={1,1};//斐波那契数列前两个元素均为0 int i=0;//循环变量 int n=20;//数列需要 ...

  5. C语言编程求fibonacci前20项,c语言,编程实现,求斐波那契数列,1,1,2,3,5,8,......的前20项及前20项和知道...

    C语言源程序如下: #include int main() { int array[100]={1,1};//斐波那契数列前两个元素均为0 int i=0;//循环变量 int n=20;//数列需要 ...

  6. c语言输出斐波那契数列前20项,在c语言中,如何利用数组求斐波那契数列的前20项?...

    在c语言中,以vc为例利用数组求斐波那契数列的前20项的具体步骤如下: 1.首先,打开vc: 2.点击文件.新建: 3.选择win32 console application 并在右侧输入工程的名字和 ...

  7. 用php递归求fibonacci数列,C++_C语言求Fibonacci斐波那契数列通项问题的解法总结,一:递归实现使用 - phpStudy...

    C语言求Fibonacci斐波那契数列通项问题的解法总结 一:递归实现  使用公式f[n]=f[n-1]+f[n-2],依次递归计算,递归结束条件是f[1]=1,f[2]=1. 二:数组实现  空间复 ...

  8. c语言斐波纳契数列判断素数,求斐波那契数列和素数

    所谓斐波那契数列指的是数列:1,1,2,3,5,8,13,21,--.用语言描述就是后一项等于前两项和. 打印100以类的斐波那契数列 a = 1 b = 1 print(a) print(b) wh ...

  9. C语言——递归函数(求斐波那契数列第n项的值)

    递归函数是指在一个函数中直接或者间接调用函数本身. 例如,设一个无返回值函数为fun,同时,设置一个整形形参变量为a ​ void fun(int a) {if(a>0){fun(a-1);pr ...

最新文章

  1. .net core在vs开发环境下脱离iis运行
  2. Ruby DSL介绍及其在测试数据构造中的使用(2)
  3. QTexe软件设置系统默认的图标
  4. 网站全屏雪花飞插件代码
  5. 移动端开发语言的未来的猜想#华为云·寻找黑马程序员#
  6. B - Beautiful Paintings
  7. 隐藏桌面上计算机图标不见了怎么办,隐藏桌面我的电脑图标不见了怎么办
  8. 5款不错的整站下载工具
  9. 如何在Mac电脑上打开终端
  10. 中国移动日渐步履蹒跚,中国电信在5G商用上取得领先优势
  11. mysql安装ecshop_ECSHOP安装流程
  12. CS下载、安装以及简单使用
  13. 解决vue项目运行npm run serve报错的问题
  14. 小程序关注微信公众号的方法
  15. 25W三星快充协议芯片,支持支持USB PD 3.0
  16. EOFError: Ran out of input
  17. 图片服务器FastDFS的安装及使用
  18. 函数的 柯里化和反柯里化
  19. mysql 实现lead_MYSQL 代替lead()的寫法?
  20. Sweetviz:让你只需三行代码实现Python探索性数据分析

热门文章

  1. 廖雪峰的GIT教程-读书笔记
  2. Crane: 腾讯开源一款基于 FinOps 超强大云资源分析与成本优化平台
  3. 阿里天蝎计划 服务器部署效率提升10倍
  4. python之路——进程
  5. R 多变量数据预处理_R语言常用统计方法包+机器学习包(名称、简介)
  6. 软件测试面试常见问题(1)
  7. 中国电信中兴F412光猫——IPTV与网络单线复用
  8. 如何屏蔽csdn百度广告
  9. rj45管脚定义_以太网接口rj45引脚定义图
  10. 嵌入式C设计模式---职责链设计模式