斐波那契 數列變形
做到一個有意思的題:
一只青蛙一次可以跳上1級臺階,也可以跳上2級,該青蛙跳上一個n級的臺階總共有多少種跳法?
首先來理一下思路,做幾個假設先:
從上圖是不是可以看出些什么?當n=1時,跳法為1種,n=2時跳法為2種,n>2時,我們可以把n級臺階的跳法不同總數看成是n的函數f(n),f(n)=f(n-1)+f(n-2)
這個實質上就是斐波那契數列的簡單變形,不知道斐波那契是什么的小伙伴可以移步至文首點擊鏈接。
看到以上數學公式馬上可以想到最簡單的一種寫法來解決這個問題:
- 方法一:遞歸
function fib(n) {
return n < 3 ? n : (fib(n - 1) + fib(n - 2))
}
結果如下
優點:代碼簡潔易懂
缺點:重復運算導致運算量太大,效率極其低下,損耗性能,遞歸的次數太多時就會超出棧的容量導致調用棧溢出,而棧溢出可能導致計算出錯誤的數據,不推薦!!!
既然遞歸的方法不可取,那有沒有更高效的辦法呢?
如果我們將計算過的斐波那契數存入數組中,那么當要計算一個斐波那契數時只需要通過下標的方式找到它前兩個數字相加就可以了
- 方法二:循環
function fib(n) {
var temp = []
if (n == 1 || n == 2) {
return n
} else {
temp[1] = 1
temp[2] = 2
for (var i = 3; i <= n; i++) {
temp[i] = temp[i - 1] + temp[i - 2]
}
return temp[i - 1]
}
}
結果如下:
優點:效率高,避免重復計算,傳入n=1000,只需要1ms就出現結果
缺點:循環寫法用空間換時間,如果n足夠大我們則需要開辟足夠大的內存空間用來存儲這個數組,也不是很可取
是不是可以不用存儲所有的斐波那契數,只需要把我們要求的前兩個斐波那契存儲下來就可以了呢?答案是肯定的
- 方法三:定義臨時變量
function fib(n) {
var num1 = 1, num2 = 1, num3 = 1
for (var i = 2; i <= n; i++) {
num3 = num1 + num2
num1 = num2
num2 = num3
}
return num3
}
結果如下:
優點:與循環寫法一樣,效率高,且只用存儲前兩個變量,省內存,省時間,可以說是非常完美了。
如果說不讓使用臨時變量呢,有這么苛刻的人嗎?可是萬一有呢【手動滑稽】
有也可以解決的啦~
- 方法四:一加一減解決問題
function fib(n) {
var num1 = 1, num2 = 1
for (var i = 2; i <= n; i++) {
num1 = num1 + num2
num2 = num1 - num2
}
return num1
}
結果如下:
細心的你會發現,誒,為什么同樣傳入的n值為500,為什么前面得到的結果是2.2559151616193602e+104,而現在得到的卻是2.2559151616193665e+104呢?
這就要說到瀏覽器計算的精度問題了,由于計算機是用二進制來存儲和處理數字,無法精確表示浮點數,連自身都不能精確,運算起來就更加得不到精確的結果了,因此這種精度差異幾乎出現在所有的編程語言中(例如C/C++/C#,Java),準確的說:“使用了IEEE 754浮點數格式”來存儲浮點類型(float 32,double 64)的任何編程語言都有這個問題,而C#、Java是因為提供了封裝類decimal、BigDecimal來進行相應的處理才避開了這個精度差異。而javascript是一種弱類型的腳本語言,本身并沒有對計算精度做相應的處理。
舉個簡單的栗子:
值越大誤差越大,當計數變為科學計數法后,運算一次就有一次誤差,上例中,加法一次誤差,減法一次誤差,導致最后呈現出的結果有些不一樣,而實際的結果卻沒有問題,看以下在ruby中測試的結果~
以上結果測試的是n=500時方法三和方法四給的結果
可以看出,在不用科學計數法表示數字的情況下,精度就可以得到保證了,而我想知道的只是第四種方法得到的結果有沒有問題,答案是沒有問題,可以放心使用。
優點:非常完美,除了科學計數法表示的精度差別
缺點:值過大時得到的是科學計數法表示的結果,存在誤差,n的值越大誤差越大,然鵝實際結果是沒有問題滴,可以大膽使用。
綜上所述,還是推薦方法三,畢竟如果沒有龜毛的人提出不準使用臨時變量這種需求,它完全是完美的。
那么~來擴展一題:一只青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
首先還是窮舉法:
雖然很蠢但這種找規律的方法很快呀
代碼如下
function fibBT(n){
return Math.pow(2,n-1)
}
結果如下:效率也是非常高呢
做完后我在網上看了很多關于變態跳的解法,幾乎都是千篇一律說變態跳是斐波那契引申問題,分析過程繁雜且不清晰,字數太多我一時還沒看明白,所以感覺不太滿意,我的代碼經自己多次測試沒有發現問題,如果你有查到bug歡迎隨時指正,一定虛心接受。
20171024補充一題
用2*1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
本題依舊是斐波那契數列變形
//n=1,f(n)=1,n=2,f(n)=2,n>2,f(n)=f(n-1)+f(n-2)
function recFib(n) {
var num1 = 0, num2 = 1, num3
if (n >= 1) {
for (var i = 0; i < n; i++) {
num3 = num1 + num2
num1 = num2
num2 = num3
}
return num3
}
}
console.log(recFib(1))//1
console.log(recFib(2))//2
console.log(recFib(3))//3
console.log(recFib(4))//5
console.log(recFib(5))//8
console.log(recFib(6))//13
console.log(recFib(7))//21
console.log(recFib(8))//34
console.log(recFib(9))//55
console.log(recFib(500))//2.2559151616193602e+104
推薦閱讀: