劍指offer-面試9:斐波那契數列(遞歸和循環)

如果需要重複地多次計算相同的問題,通常可以選擇用遞歸或者循環兩種不同的方法。遞歸是在一個函數的內部調用這個函數自身。而循環則是通過設置計算的初始值及終止條件,在一個範圍內重複運算。
通常遞歸的代碼會比較簡潔。在上面的例子裏,遞歸的代碼只有一個語句,而循環則需要4個語句。在樹的前序、中序、後序遍歷算法的代碼中,遞歸的實現明顯要比循環簡單得多。在面試的時候,如果面試官沒有特別的要求,應聘者可以儘量多采用遞歸

遞歸雖然有簡潔的優點,但它同時也有顯著的缺點。遞歸由於是函數調用自身,而函數調用是有時間和空間的消耗的:每一次函數調用,都需要在內存棧中分配空間以保存參數、返回地址及臨時變量,而且往棧裏壓入數據和彈出數據都需要時間。這就不難理解上述的例子中遞歸實現的效率不如循環。

另外,遞歸中有可能很多計算都是重複的,從而對性能帶來很大的負面影響。遞歸的本質是把一個問題分解成兩個或者多個小問題。如果多個小問題存在相互重疊的部分,那麼就存在重複的計算。
除了效率之外,遞歸還有可能引起更嚴重的問題:調用棧溢出。前面分析中提到需要爲每一次函數調用在內存棧中分配空間,而每個進程的棧的容量是有限的。當遞歸調用的層級太多時,就會超出棧的容量,從而導致調用棧溢出。

題目

寫一個函數,輸入n,求斐波那契(Fibonacci)數列的第n項。斐波那契數列的定義如下:
這裏寫圖片描述

分析

效率很低的解法,挑剔的面試官不會喜歡

很多的C語言教科書在將遞歸函數的時候,都會用Fibonacci作爲例子,因此很多應聘者對這道題的遞歸解法都很熟悉

long long Fibonacci( unsigned int n )
{
    if( n<= 0 )
        return 0;
    if( n==1 )
        return 1;
    return Fibonacci( n-1 ) + Fibonacci( n-2 );
}

上述遞歸的解法有很嚴重的效率問題並要求我們分析原因。

以求解 f( 10 )爲例來分析遞歸的求解過程。想求得 f(10),需要先求得 f(9) 和f(8)。同樣,想求得 f(9),需要先求得 f(8) 和 f(7)。。。可以用樹形結構來表示這種依賴關係。
這裏寫圖片描述

可以發現這棵樹中有很多結點時重複的,而且重複的結點數會隨着n的增大而急劇增加,這意味計算量會隨着n的增大而急劇增大。事實上,用遞歸方法計算的時間複雜度是以n的指數的方式遞增的。

面試官期待的實用解法

其實改進的方法並不複雜,上述遞歸代碼之所以慢是因爲重複的計算太多,只要想辦法避免重複計算就行了。比如可以把已經等到的數列中間項保存起來,如果下次需要計算的時候我們先查找一下,如果前面已經計算過就不用再重複計算了。

更簡單的辦法是從下往上計算,首先根據f(0)和f(1)計算出f(2),再根據f(1)和f(2)算出f(3)。。。依次類推就可以算出第n項了。很容易理解,這種思路的時間複雜度是O(n)。

long long Fibonacci( usigned n )
{
    int result[2] = { 0,1 };
    if( n<2 )
        return result[ n ];
    long long fibNMinusOne = 1;
    long long fibNMinusTwo = 0;
    long long fibN = 0;
    for( usigned int i=2; i<n; ++i )
    {
        fibN = fibNMinusOne + fibNMinusTwo;
        fibNMinusTwo = fibNMinusOne;
        fibNMinusOne = fibN;
    }
    return fibN;        
}

時間複雜度O(logn)但不夠實用的解法

通常面試到這裏也就差不多了,儘管我們還有比這更快的O(logn)算法。由於這種算法需要用到一個很生僻的數學公式,因此很少有面試官會要求我們掌握。以備不時之需。

這裏寫圖片描述

解法比較

用不同的方法求解斐波那契數列的時間效率大不相同。
第一種基於遞歸的解法雖然直觀但時間效率很低,在實際軟件開發中不會用這種方法。
第二種方法把遞歸的算法用循環實現,極大地提高了時間效率。
第三種方法把求斐波那契數列轉換成求矩陣的乘方,是一種很有創意的算法。雖然可以用O(logn)求得矩陣的n次方,但由於隱含的時間常數較大,很少有軟件會採用這種算法。另外,這種解法的代碼也很複雜,不太實用面試。

測試用例&代碼

(1)功能測試(如輸入3、5、10等)

(2)邊界值測試(如輸入 0、1、2)

(3)性能測試(輸入較大的數字,如40、50、100等)

本題考點

(1)對遞歸、循環的理解及編碼能力

(2)對時間複雜度的分析能力

(3)如果面試官採用的是青蛙跳臺階的問題,那同時還在考查應聘者的數學建模能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章