题目一:写一个函数,输入n,求斐波那契数列的第n项。斐波那契数列的定义以下:面试
一、效率很低效的解法,挑剔的面试官不会喜欢算法
不少C语言的教科书在讲述递归函数的时候,都户拿Fibonacci做为例子,所以不少的应聘者对这道题的递归解法都很熟悉。函数
下面是实现代码spa
咱们教科书上反复用这个问题来说解递归的函数,并不能说明递归的解法最适合这道题目。面试官会提示咱们上述递归的解法有很严重的效率问题要求咱们分析缘由。递归
咱们以求解f(10)为例来分析递归的求解过程。想求得f(10),须要先求出f(9)和f(8).一样求f(9),须要先求得f(8)和f(7)。咱们用树来构造这种依赖关系。如图所示:ci
咱们不难发如今这颗树中有不少的节点是重复的,并且重复的节点数会随 着n的增大而急剧增长,这意味着计算量会随着n的增大而急剧增大。事实上,用递归的方法计算的时间复杂度是以n的指数的方式递增的。读者不妨求 Fibonacci的第100项试试,感觉一下这样的递归会慢到什么程度。开发
效率低的:数学
package cglib;it
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));
}
}table
输出:
832040
若是是求第5000个,会很慢很慢很慢。。。。。
二、面试官期待的适用解法:
其实改进的方法比并不复杂。上述的递归代码之因此慢是由于重复的计算太多,咱们只要想避免重复计算就型了。好比咱们能够把已经获得的数列中间项保存起来,若是下次须要计算的时候咱们先查找一下,若是前面已经计算过就不用重复计算了。
更简单的方法是从下往上计算,首先计算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
还挺快的
三、时间复杂度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比较慢