1. 程式人生 > >LeetCode刷題Easy篇斐波那契數列問題(遞迴,尾遞迴,非遞迴和動態規劃解法)

LeetCode刷題Easy篇斐波那契數列問題(遞迴,尾遞迴,非遞迴和動態規劃解法)

題目

斐波那契數列: 
f(n)=f(n-1)+f(n-2)(n>2) f(0)=1;f(1)=1; 
即有名的兔子繁衍問題 

1 1 2 3 5 8 13 21 ....

我的解法

遞迴

public static int Recursion(int n){

        if(n==1){
            return 0;
        }

        if(n==2){
            return 1;
        }
        return Recursion(n-1)+Recursion(n-2);
    }

è¿éåå¾çæè¿°

圖片轉自https://blog.csdn.net/MallowFlower/article/details/78858553

尾遞迴

遞迴時間複雜度比較高。遞迴不停地壓棧和出棧,時間和空間都有很大的消耗,

如果一個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。尾遞迴函式的特點是在迴歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的程式碼。

簡單理解,就是處於函式尾部的遞迴呼叫本身的情形下,前面的變數狀態都不需要再儲存了,可以釋放,從而節省很大的記憶體空間。在前面的程式碼中,明顯在呼叫遞迴呼叫Fibonacci(n-1)

的時候,還有Fibonacci(n-2)沒有執行,需要儲存前面的狀態,因此開銷較大的

public int lastFibonacci(int n, int ret1, int ret2) {
    if(n == 1) {
        return ret1;
    }
    return lastFibonacci(n - 1, ret2, ret1 + ret2);
}

ret1和ret2初始傳入都為1,其實跟我下面的迭代方法思路一樣,無非就是遞迴實現。關鍵點在於遞迴呼叫處的ret2就是n的值。因為遞迴呼叫處的ret2是上次呼叫的ret1+ret2,尾部遞迴每次把和傳給下次呼叫。

 

迭代方法

這個方法類似於爬樓梯問題,詳見我的部落格爬樓梯演算法。

int first=1;
int second=2;
int res=0;
for(int i=3;i<=n; i++){
    res=first+second;
    first=second;
    second=res;
}
return res;

動態規劃

這道題目不是leetcode上的,主要是為了複習遞迴的思路和動態規劃的思路,加強練習。

 public static int fib(int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=3;i<=n;i++){
            dp[i-1]=dp[i-2]+dp[i-3];
        }
        return dp[n-1];

    }

    /* Driver program to test maxSubArraySum */
    public static void main(String[] args) {
        int n=8;
        int res = fib(n);
        System.out.println("solution is "
                + res);
    }

這個題目用動態規劃解決時,我們只需要最近一次的和,所以沒有必需用陣列記錄所有的中間和,可以節省空間,利用上面的迭代方法解決,是最優的解決方式,但是,這個動態規劃的基本步驟和思路在其他問題中需要記錄中間值,比如最大和的子陣列問題等,需要記錄中間的和,比較哪個最大。