1. 程式人生 > 其它 >力扣 - 劍指 Offer 10- II. 青蛙跳臺階問題

力扣 - 劍指 Offer 10- II. 青蛙跳臺階問題

題目

劍指 Offer 10- II. 青蛙跳臺階問題

思路1(遞迴 / 自頂向下)

這題就是和上一題劍指 Offer 10- I. 斐波那契數列基本一模一樣,都是 fib(n) = fib(n-1) + fib(n-2) 這個遞推公式,只是初始條件不一樣啦:上一題是 0、1、1、2...,而本題是 1、1、2、3、5...

  • 這題是很常見的一道入門遞迴題,可以採用自頂向下的遞迴方法,比如我們要求第n個位置的值,根據斐波那契數列的定義fib(n) = fib(n-1) + fib(n-2),即等於前一個和前前一個兩個的值之和

  • 但是如果直接遞迴,會導致很多重複的計算,效率很低,比如 n 為 5 時:

    1. fib(5)fib(4)fib(3) 兩個值之和

    2. 然後 fib(4) 又等於 fib(3)fib(2) 兩個值之和。注意, fib(3) 在上一步已經求過了,這裡還要再求一次

    3. 另一個 fib(3) 即為 fib(2)fib(1) 兩個值之和,同樣,fib(2),也被求過了

    4. ……

  • 根據上面例子我們可以發現這樣子會導致很多多餘的計算,做無用功,也會出現由於 n 的增大導致計算量急劇增大。因此我們可以將這個演算法優化一下,就是新增一個表格 memory 來記錄計算過的值,在每次遞迴的時候,判斷一下之前是否計算過了,如果發現計算過了,直接返回陣列中對應的值,否則就計算一下,然後記錄到 memory

    表格裡

程式碼

class Solution {

    int[] memory;

    public int numWays(int n) {
        memory = new int[n+1];
        return help(n);
    }

    public int help(int n) {
        // 遞迴結束的條件
        if (n <= 1) {
            return 1;
        }

        // 判斷是否計算過了
        if (memory[n] != 0) {
            return memory[n];
        }

        // 沒有在 memory 中找到就計算一下,然後在記錄到 memory 中
        int i = help(n - 1) + help(n - 2);
        i %= 1000000007;
        memory[n] = i;

        return memory[n];
    }
}

複雜度分析

  • 時間複雜度:\(O(N)\)
  • 空間複雜度:\(O(N)\)

思路2(迭代 / 動態規劃)

  • 同樣,根據斐波那契數列定義,可以發現第 n 個的值為前兩個值之和,因此我們可以從第一個開始計算,迴圈計算到 n就得到了結果,空間上僅僅佔兩個變數的空間,為 \(O(1)\) ,程式碼如下:

程式碼

class Solution {
    public int numWays(int n) {
        if (n <= 1) {
            return 1;
        }

        int a = 1;
        int b = 2;

        for (int i = 2; i < n; i++) {
            int temp = (a + b);
            a = b;
            b = temp;
            b %= 1000000007;
        }

        return b;
    }
}

複雜度分析

  • 時間複雜度:\(O(N)\)
  • 空間複雜度:\(O(1)\)
我走得很慢,但我從不後退!