題目一:寫一個函數,輸入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比較慢