青蛙跳

斐波那契 數列變形

做到一個有意思的題:

一只青蛙一次可以跳上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時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

推薦閱讀

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 原文鏈接:http://blog.csdn.net/qq_22329521/article/details/529...
    越長越圓閱讀 1,597評論 0 1
  • 一只青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。解:1個臺階:12個臺階...
    MAXPUP閱讀 392評論 0 0
  • 一只青蛙一次可以跳上1級臺階,也可以跳上2級臺階。求該青蛙跳上n級臺階一共有多少種方法?思路:假設1級臺階有f(1...
    江小修閱讀 818評論 0 0
  • 大家好,我是144號星寶寶羅莉,正在參加小牛媽媽舉辦的第10期21天寫作訓練的蛻變之旅第20天,這是我第188篇原...
    羅文均閱讀 128評論 0 1
  • 轉眼特種兵的學習已經兩周過去了,這種軍事化的訓練,比我想象中的難太多。第一次的訓前作業就搞得我頭暈腦脹喘不過氣來的...
    冰雨_2bd4閱讀 225評論 0 0