面试题10:斐波那契数列
题目一:求斐波那契数列的第n项。
写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。斐波那契数列的定义如下:
分析:
- 方法一:递归方式,但效率不高,因为在斐波那契数列计算过程中重复的计算太多,因此时间复杂度是以n的指数方式递增的。
long long Fibonacci(unsigned int n)
{
if(n = 0)
return 0;
if(n = 1)
return 1;
if(n>1)
return Fibonacci(n) = Fibonacci(n-1)+Fibonacci(n-1);
}
- 方法二:递推方式:改进的方法,从下往上去计算,每计算一层就保存一层数据。如图所示以求
f(3)
为例:
从下往上计算则先计算f(1) + f(0) = f(2) 即 1+0 = 1 = f(2)
;
然后计算f(2) + f(1) = f(3) 即 1+1 = 2 = f(3)
;
可以看出,每回计算完下一层的f(n-1)+f(n-2)
之后就存值在上一层的f(n-1)
处;
并且下一层的f(n-1)
值和上一层的f(n-2)值是一样的,直接赋值给上一层的f(n-2)
即可。
于是有了如下的代码段:
fN = fNMinus1 + fNMinus2;
fNMinus2 = fNMinus1;
fNMinus1 = fN ;
求斐波那契数列的代码:
long long Fibonacci(unsigned int n)
{
int r[2] = {0, 1};
if(n<2)
return r[n];
long long fN=0;
long long fNMinus1 = 1;
long long fNMinus2 = 0;
for(int i=2;i<=n;i++)
{
fN = fNMinus1 + fNMinus2;
fNMinus2 = fNMinus1;
fNMinus1 = fN ;
}
return fN;
}
递归和循环
对于那些需要重复的多次计算相同的问题,则通常可以选择递归或者循环两种不同的方法。
递归
- 递归就是在一个函数的内部去调用这个函数自身。
计算1+2+3+4+…+n:
int AddFrom1ToN(int n)
{
return n<=0? 0:n + AddFrom1ToN(n-1);
}
-
在树的前序、中序、后序遍历算法的代码中,递归的实现明显要比循环简单的多。
-
递归的优点就是代码简洁;缺点就是效率不高,并且占用内存大。因为递归操作每回在调用函数的时候都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而且往往栈里压入数据和弹出数据都是需要时间的。
-
递归的本质就是将一个问题分解成两个或者多个小问题。如果多个小问题存在相互重叠的部分,就出现了重复计算。
通常应用动态规划解决问题时,我们都是用递归的思路分析问题,由于总出现重复计算,因此总采用自下而上的循环来实现。 -
递归还可能会出现调用栈溢出(错误)。每次函数调用都需要在内存栈中分配空间,而每个进程的栈的容量是有限的。递归调用的层次太多时,就会超出栈的容量,从而导致栈溢出。
栈溢出是由于C语言系列没有内置检查机制来确保复制到缓冲区的数据不得大于缓冲区的大小,因此当这个数据足够大的时候,将会溢出缓冲区的范围。在Python中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
循环
循环就是通过设置初始值和终止条件,在一个范围内重复的运算。
计算1+2+3+4+…+n:
int AddFrom1ToN(int n)
{
int r = 0;
for(int i=0;i<n;i++)
{
r += i;
}
return r;
}
斐波那契数列的介绍
斐波那契数列(Fibonacci sequence):又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。最早被提出是印度数学家Gopala,他在研究箱子包装物件长度恰好为1和2时的方法数时首先描述了这个数列。
指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
斐波那契数列的应用:青蛙跳台阶问题
题目描述: 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级台阶总共有多少种跳法。
分析:
设f
为青蛙跳上n级台阶时的跳法与n之间的对应函数。
那么,n=0时f = 0
;
当n=1时f为f = 1
;
当n=2时f=2
;
n大于2时,若第一次只跳一级,此时的跳法就等于后面剩下的n-1个台阶的跳法;若第一次只跳2级,此时的跳法就等于后面剩下的n-2个台阶的跳法。所以最终:f(n) = f(n-1) + f(n-2)
。
笔记
long关键字
long关键字表示一种长整型数据,是编程语言中的一种基本数据类型,为long int 的缩写,默认为有符号长整型,含4个字节,取值范围为:-2^31 ~ (2^31 -1)
。
用long long 来定义数据: long long是C99标准新加的,64位长整形,某些老古董编译器不支持该类型,所以如果要使用的话要注意编译器对C标准的支持,如果不支持可以使用编译器自定义的长整形(如VC的__int64)。
unsigned
unsigned的作用就是将数字类型无符号化, 例如 int 型的范围:-2^31 ~ (2^31 - 1)
,而unsigned int的范围:0 ~ 2^32
。看起来unsigned 是个不错的类型,尤其是用在自增或者没有负数的情况。但是在实际使用中会出现一些意外的情况。
无符号版本和有符号版本的区别就是无符号类型能保存2倍于有符号类型的正整数数据,比如16位系统中一个short能存储的数据的范围为-32768~32767
,而unsigned能存储的数据范围则是0~65535
。
另外,unsigned若省略后一个关键字,大多数编译器都会认为是unsigned int。unsigned int取值范围为0~4294967295
(32位)。