引言
0、1、1、2、3、5、8、13、21
說來慚愧,第一次認識斐波那契數列是在一次面試題中,當時稍微觀察了一下,就果斷地寫出了迭代版本的斐波那契數列…當時,還覺得這個算法題的數學模型也太簡單了吧,殊不知有時候問題並不是你解決了就可以,毫無疑問對於斐波那契數列有多種解法,對於迭代版本的斐波那契數列來說它可能是多個版本中效率最高,但是我們並不能被迭代版本所限制住我們用編程解決問題的思維。
迭代版
既然最先提及迭代版本的斐波那契數列,那麼我們就來看看它的實現:
function fibonacciInterative(n) {
if (n < 1) return 0
if (n <= 2) return 1
let fibNMinus2 = 1
let fibNMinus1 = 1
let fibN = n
for (let i = 3; i <= n; i++) {
fibN = fibNMinus1 + fibNMinus2
fibNMinus2 =fibNMinus1
fibNMinus1 = fibN
}
return fibN
}
其實,迭代版本的思路最白話地貼近我們的思路,首先對於索引爲 0 1 的數值並沒有規律,所以我們可以在方法中單獨判斷這種情況,返回對應的值(這裏把索引爲 2 的也加入,是因爲都是返回 1,所以也無傷大雅一起返回),然後,對於其他的數學模型就很簡單了,當前索引的值等於前兩個索引的值之和,所以 for 循環遍歷當前索引之前的數,一直求和以及賦值,最終求出當前索引下標對應的值。
遞歸版
對於遞歸,我想很多從事編程的同學都瞭解,但是目前我見到的很少場景會用到遞歸,除了早期在學數據結構的時候樹用的比較多。不過,對於前端開發的同學我想對遞歸應該很是熟悉,因爲 DOM 樹 和 CSSOM 的緣故,在一些框架中設計 VNode 以及一些對象的時候都是樹形結構,這個時候就避免不了遞歸遍歷的過程。那麼,我們來看看遞歸版的斐波那契數列:
function fibonacciRecursion(n) {
if (n < 1) return 0
if (n <= 2) return 1
return fibonacciRecursion(n-2) + fibonacciRecursion(n - 1)
}
對比起來迭代版本,是不是感覺遞歸版本看起來簡潔了許多。但是,仔細思考,會發現遞歸版本,會發生一種情況,由於它並沒有存計算過的值,所以會出現重複計算相同索引地情況出現,這個過程無疑會浪費性能。
迭代+記憶版
瞭解了迭代,對於突然出現的名詞記憶,我想大家可能會覺得有點突兀,其實對於記憶可以理解爲緩存,通常是用一個數組來存一些值,然後避免重複計算或執行的過程(例如,在 Vue 中會緩存組件,避免組件的重複創建)。所以,迭代+記憶版的斐波那契數列會是這樣的:
function fabonacciMemoization(n) {
const memo = [0, 1]
const fibonacci = (n) => {
if (memo(n)) return memo(n)
return memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
}
return fibonacci
}
對比起迭代版,記憶版避免了相同索引值的重複計算,並且和迭代版比起來,它也非常簡潔,指得推薦。