Thinking——斐波拉契數列不用遞歸

斐波拉契數列

這個數列生成規則很簡單,每一項都是前兩項的和,舉例
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……
用數學符號來描述更好算法

$$ F_{n}=F_{{n-1}}+F_{{n-2}}(n≧2) $$數組

遞歸方法求解

這個幾行代碼就能夠解決函數

// n從1開始
function fib(n) {
    if (n <= 2)
        return 1;
    return fib(n - 1) + fib(n - 2);
}

可是分析一下這個算法的執行過程,以f(6)爲例
圖片描述
以上的遞歸樹中,不少節點被重複計算,譬如f(2)就被重複執行了5次,另外,遞歸調用中每個函數都要保留上下文,因此空間上開銷也不小。因此這個方法並非很理想。spa

循環方法求解

其實仍是利用斐波拉契數列的特性code

// n從1開始
function fibFor(n) {
    if (n <= 2)
        return 1;
    let arr = [];
    //arr[0] = 0;
    arr[2] = arr[1] = 1;
    for (let i = 3; i <= n; i++) {
        arr[i] = arr[i - 1] + arr[i - 2];
    }
    return arr[n];
}

固然這個方法還能夠壓縮一下,由於咱們不必存儲一個數組,只用三個變量就好了。blog

// n從1開始
function fibFor2(n) {
    if (n <= 2)
        return 1;
    let before2 = 1;
    let before1 = 1;
    let now = null;
    for (let i = 3; i <= n; i++) {
        now = before1 + before2;
        before2 = before1;
        before1 = now;
    }
    return now;
}

這其實就是動態規劃的思想了,自底向上,用子問題解決父問題。遞歸

進一步:LCS問題

最長公共子序列(Longest Common Subsequence)是指,給出兩個字符串,找到最長公共子序列(LCS),返回LCS的長度,注意是子序列,不是子串。
舉個例子:
對於」ABCD」 和 「EDCA」,這個LCS是 「A」 (或 D或C),返回1
對於「ABCD」 和 「EACB」,這個LCS是「AC「(或」AB「)返回2圖片

思路

對於這個問題,設字符串a長度爲m,字符串b爲n,考察它們的最後一個字符串,若是a[m]==b[n],則Longestm = Longestm-1,不然,Longestm = Max(Longestm-1, Longestm)。找到這個關係後,首先想到的是用遞歸方法解決問題:內存

package main

import "fmt"

func max(x, y int) int {
    if x > y {
        return x
    }
    return y
}

func LongestCommonSequence(a string, b string) int {
    aChars := []rune(a)
    bChars := []rune(b)
    aLen := len(aChars)
    bLen := len(bChars)
    if aLen == 0 || bLen == 0 {
        return 0
    }
    if aChars[aLen-1] == bChars[bLen-1] {
        return LongestCommonSequence(string(aChars[:aLen-1]), string(bChars[:bLen-1])) + 1
    } else {
        return max(
            LongestCommonSequence(string(aChars[:aLen-1]), b),
            LongestCommonSequence(a, string(bChars[:bLen-1])))
    }
}

func main() {
    a := "ACDE"
    b := "CBE"
    fmt.Printf("Longest sub sequence length of %s and %s is %d\n", a, b,
        LongestCommonSequence(a, b))
}

這樣的解法跟上面斐波拉契的解法同樣,會遇到子問題重複計算,並且遞歸棧要保存臨時結果,會佔用較大的內存。因此,咱們也能夠思考一下使用自底而上的方法。考察一下遞歸表達式,它涉及到兩個變量(m,n),因此咱們能夠用一個二維數組來保存以前的結果。
實現:字符串

func LongestCommonSequenceDP(a string, b string) int {
    aChars := []rune(a)
    bChars := []rune(b)
    aLen := len(aChars)
    bLen := len(bChars)
    if aLen == 0 || bLen == 0 {
        return 0
    }

    record := make([][]int, aLen+1)
    for i := 0; i <= aLen; i++ {
        record[i] = make([]int, bLen+1)
        record[i][0] = 0
    }

    for j := 0; j <= bLen; j++ {
        record[0][j] = 0
    }

    for i := 1; i <= aLen; i++ {
        for j := 1; j <= bLen; j++ {
            if aChars[i-1] == bChars[j-1] {
                record[i][j] = record[i-1][j-1] + 1
            } else {
                record[i][j] = max(record[i][j-1], record[i-1][j])
            }
        }
    }

    return record[aLen][bLen]
}
相關文章
相關標籤/搜索