面試題9:斐波那契數列


題目1:寫一個函數,輸入n,其斐波那契數列的第n項。

斐波那契數列的定義如下:

  • F_0=0
  • F_1=1
  • F_n = F_{n-1}+ F_{n-2}

方法1:使用遞歸解,時間復雜度是n的指數級別

斐波那契數列的定義就是遞歸的,我們根據定義可以很簡單的寫出代碼。代碼如下:

View Code
#include<iostream>
#include<stdlib.h>
using namespace std;

//f(n)={0,1,1,2,3...} n>=0

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

void main()
{
    int f=Fibonacci(30);
    cout<<f<<endl;

    system("pause");
}

但是這樣的方法存在明顯的不足,該方法的時間復雜度是n的指數級別,隨着n的增大,運算時間不可想象,比如說f(50)就要很久。時間復雜度之所以這么大,是因此計算過程中存在着重復計算。以f(10)為例,f(10)=f(9)+f(8),f(9)=f(8)+f(7)。其中的f(8)就是重復計算的。

方法2:開辟一個長度為(n+1)的數組,時間復雜度為O(n),空間復雜度為O(n)

前面我們計算斐波那契數列是從后往前計算的,就是計算f(n)=f(n-1)+f(n-2),然后再遞歸計算f(n-1),又是從后往前計算,就是因為這樣的從后往前計算,所以才會有很多的重復計算。那么我們可以逆轉思路,考慮從前往后計算。比如我們要計算f(4),那么我們就計算f(0)、f(1)、f(2)、f(3),將這些計算出來的值保存在一個數組arry[n+1]上,這樣計算斐波那契數列就相當於是一個填表的過程。時間復雜度大大降低。代碼實例如下:

View Code
int Fibonacci(int n)
{
    if(n<=0)
        return 0;
    else if(n==1)
        return 1;
    else
    {
        //動態創建一個長度為(n+1)的數組
        int *arry=new int[n+1];
        arry[0]=0;
        arry[1]=1;
        for(int i=2;i<=n;i++)
        {
            arry[i]=arry[i-1]+arry[i-2];
        }
        int result=arry[n];
        //因為動態創建的數組不會因為出了作用域,內存就會被釋放。
        //動態分配的數組將一直存在,直到程序顯示釋放它為止,因此這里使用delete []
        //c++提供delete []表達式釋放指針所指向的數組空間。
        delete [] arry;
        return result;
    }
}

注意點:

  1. 因為不知道要求的f(n)中的n有多大,因此不能事先開辟一個數組,需要動態創建數組。而動態數組與數組變量不同,動態分配的數組將一直存在,直到程序顯式釋放它為止。普通的數組變量,只要出了數組的作用於,其內存會自動釋放。
  2. c++提供delete []表達式釋放指針所指向的數組空間。delete [] arry;該語句回收了arry所指向的數組,把相應的內存返回給自由存儲區。在關鍵字delete和指針arry之間的方括號[]是必不可少的:它告訴編譯器該指針指向的是自由存儲區中的數組,而非單個對象。delete arry只釋放了arry指針所指向的內存地址,理論上來說會少釋放了內存空間,從而產生內存泄露。

方法3:優化方法2,空間復雜度為O(1),時間復雜度為O(n)

在方法2中,我們保存了每一個中間變量,但是仔細觀察我們可以發現沒有必要保存每一個中間變量,我們只需要保存兩個臨時變量即可完成斐波那契數列的計算。具體代碼試下如下:

View Code
int Fibonacci(int n)
{
    if(n<=0)
        return 0;
    else if(n==1)
        return 1;

    else
    {
        //當n>=2時,初始化pre=f(0)=0,post=f(1)=1,f(n)=0;
        int pre=0;
        int post=1;
        int fn=0;
        //采用循環計算斐波那契數列,通過兩個臨時變量pre和post保存中間結果,避免重復計算
        for(int i=2;i<=n;i++)
        {
            fn=pre+post;//fn等於其前面兩個元素值的和
            //然后讓pre和post分別直線他們后面的元素。
            pre=post;
            post=fn;
        }
        return fn;
    }
}

 

題目2:一只青蛙一次可以跳上1級台階,也可以跳上2級台階。求該青蛙跳上一個n級的台階總共有多少種跳法。

這道題目的本質就是斐波那契數列。假設只有一個台階,那么只有一種跳法,那就是一次跳一級,f(1)=1;如果有兩個台階,那么有兩種跳法,第一種跳法是一次跳一級,第二種跳法是一次跳兩級,f(2)=2。如果有大於2級的n級台階,那么假如第一次跳一級台階,剩下還有n-1級台階,有f(n-1)種跳法,假如第一次條2級台階,剩下n-2級台階,有f(n-2)種跳法。這就表示f(n)=f(n-1)+f(n-2)。將上面的斐波那契數列代碼稍微改一下就是本題的答案。這列略之。

 

題目3:我們可以用2X1(2行1列)的小矩形橫着或者豎着去覆蓋更大的矩形。請問用8個2X1的小矩形無重復地覆蓋一個2X8的大矩形,總共有多少種方法。
               
               
 
 

 這道題目還是斐波那契數列的變種。設f(8)表示覆蓋2X8大矩形的方法綜述。假設第一個小矩形是豎着去覆蓋大矩形,那么還剩下由7個2X1的小矩形組成的大矩形f(7);假設第一個小矩形是橫着去覆蓋大矩形,那么還剩下由6個2X1的小矩形組成的大矩形f(6)。即f(8)=f(7)+f(6)。依此類推,最后f(1)=1,f(2)=2。使用計算斐波那契數列的方法計算這道題目即可求出答案。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM