您的位置:首页 > Web前端

剑指Offer(java版)斐波那契数列 ——1,1,2,3,5,8......

2016-07-18 00:00 239 查看
题目一:写一个函数,输入n,求斐波那契数列的第n项。斐波那契数列的定义如下:



1、效率很低效的解法,挑剔的面试官不会喜欢

很多C语言的教科书在讲述递归函数的时候,都户拿Fibonacci作为例子,因此很多的应聘者对这道题的递归解法都很熟悉。

下面是实现代码



我们教科书上反复用这个问题来讲解递归的函数,并不能说明递归的解法最适合这道题目。面试官会提示我们上述递归的解法有很严重的效率问题要求我们分析原因。

我们以求解f(10)为例来分析递归的求解过程。想求得f(10),需要先求出f(9)和f(8).同样求f(9),需要先求得f(8)和f(7)。我们用树来构造这种依赖关系。如图所示:



我们不难发现在这颗树中有很多的节点是重复的,而且重复的节点数会随 着n的增大而急剧增加,这意味着计算量会随着n的增大而急剧增大。事实上,用递归的方法计算的时间复杂度是以n的指数的方式递增的。读者不妨求 Fibonacci的第100项试试,感受一下这样的递归会慢到什么程度。

效率低的:

package cglib;

public class List1
{
public static int find(int n){

if(n<=0)
return n=0;
else if (n==1)
return n=1;
else
return find(n-1)+find(n-2);
}
public static void main(String[] args){

System.out.println(find(30));
}
}

输出:

832040

如果是求第5000个,会很慢很慢很慢。。。。。

2、面试官期待的适用解法:

其实改进的方法比并不复杂。上述的递归代码之所以慢是因为重复的计算太多,我们只要想避免重复计算就型了。比如我们可以把已经得到的数列中间项保存起来,如果下次需要计算的时候我们先查找一下,如果前面已经计算过就不用重复计算了。

更简单的方法是从下往上计算,首先计算f(0)和f(1)算出f(2),再根据f(1)和f(2)算出f(3)……依次类推就可以算出第n项了。很容易理解,这种思路的时间复杂度为O(n)。实现代码如下:

package cglib;

public class List1
{
public static long find(long n){

long result =0;
long preOne = 1;
long preTwo = 0;
if( n == 0){
return preTwo;
}
if(n == 1){
return preOne;
}
for(int i = 2;i<= n ;i++){
result = preOne+preTwo;
preTwo = preOne;
preOne = result;
}
return result;
}
public static void main(String[] args){

System.out.println(find(5000));
}
}

输出:

535601498209671957

还挺快的

3、时间复杂度O(logn)但不够使用的解法)

通常面试到这里就差不多了,尽管我们还有比这更快的O(logn)解法,由于这种算法需要用到一个很生僻的数学公式,因此很少有面试官会要求我们掌握。不过以防万一,我们还是介绍一下这种算法。

我们先来聊i额一个数学公示:





解法比较:

用不同的方法求斐波那契数列的时间效率大不相同。第一种基于递归的解 法虽然直观但时间效率太低,实际软件开发中不会使用这种方法,也不可能得到面试官的青睐。第二种方法把递归的算法用循环来实现,极大的提高了时间效率。第 三种方法把求斐波那契数列转换成求矩阵的乘方,是一种很有创意的算法。虽然我们可以哟个O(logn)求的矩阵的n次方,但由于隐含的时间常熟较大,很少 会有软件采用这种算法,另外,实现这种算法的代码也交复杂,不太适用于面试。

相关题目:

我们可以用2*1的小矩形横着或竖着去覆盖更大的矩形。请问用8个2*1的小矩形无重叠地覆盖一个2*8的大矩形,总共有多少方法?

我们先把2*8 的覆盖方法记为f(8)。用第一个1*2小矩形去覆盖大矩形的最左边时有两个选择,竖着放或者横着放。当竖着放的时候,右边还剩下2*7的区域,这种情形 下的覆盖方法记为f(7).这是第一种选择,接下来考虑横着放的情况。1*2的小矩形横着放在左上角的时候,左下角必须也横着放一个1*2的小矩形,所以左边的 2*2 的区域 是固定这么放了,而在右边还剩下2*6 的区域,这种情形下的覆盖方法即为f(6),这是第二种选择,因此f(8)=f(7)+f(6).此时,我们可以看出,这仍然是斐波那契数列。

扩展部分:

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

用Fib(n)表示青蛙跳上n阶台阶的跳法数,青蛙一次性跳上n阶台阶的跳法数,是定Fib(0)=1;

当n = 1时,只有一种跳法,即1阶跳:Fib(1) = 1;

当n = 2时,有两种跳法,一阶跳和二阶跳:Fib(2) = Fib(1)+FIb(0) = 2;

当n =3时,有三种跳法,第一次跳出一阶后,后面还有Fib(3-1)中跳法;第一次跳出二阶后,后面还有Fib(3-2)中跳法;第一次跳出三阶后,后面还有Fib(3-3)中跳法

Fib(3)= Fib(2)+Fib(1)+Fib(0) = 4

当n= n时,共有n种跳法方式,第一次跳出一阶后,后面还有Fib(n-1)种跳法;第一次跳出二阶后,后面还有Fib(n-2)种跳法,第一次跳出n阶后,后面还有Fib(n-n)种 跳法 。

Fib(n) = Fib(n-1)+Fib(n-2)+Fib(n-3)+..........+Fib(n-n)=Fib(0)+Fib(1)+Fib(2)+.......+Fib(n-1)

又因为Fib(n-1)=Fib(0)+Fib(1)+Fib(2)+.......+Fib(n-2)

两式相减得:Fib(n)-Fib(n-1)=Fib(n-1) =====》 Fib(n) = 2*Fib(n-1) n >= 2

递归等式如下:



即f(n)=2^n-1(n>2)

package cglib;

public class List1
{
public static long find(long n){

long a=0;

if(n<0){

a= 0;

}else if(n==0){

a=1;

}else{

a=(long) Math.pow(2,n-1);
//for(long i=n-1;i>=0;i--){

//a+=find(i);

//}

}

return a;
}
public static void main(String[] args){

System.out.println(find(3));
}
}

用 a=(long) Math.pow(2,n-1); 速度更快,用for比较慢
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: