1. 程式人生 > >Leetcode 70. 爬樓梯 Java

Leetcode 70. 爬樓梯 Java

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

示例 1:

輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1.  1 階 + 1 階
2.  2 階

示例 2:

輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階

根據題意不難看出這是一道動態規劃的題目,只要寫出狀態轉移方程,我們就可以很容易的將其解決,誠然這道題的狀態轉移方程很簡單,那麼遇到更難的題目呢?在這裡想和大家通過一道例題來分析一下對於這類問題的解決思路。

首先,無從下手的話,我們不妨嘗試暴力遞迴

private int getClimbStairs(int n) {
        if (n == 1) {
            return 1;
        }

        if (n == 2) {
            return 2;
        }
        
        return getClimbStairs(n - 1) + getClimbStairs(n - 2);
    }

這樣的程式碼思路很清晰,在我們的編譯器上,也會輕鬆通過,但是提交到leetcode往往會提示超出時間限制,是因為在n大的時候,遞迴棧非常之深,每次去遞迴時間開銷很大,所以會丟擲超時。那麼怎麼去在這個思路上去改進呢? 有的同學可能會發現,在遞迴的時候,不斷有重複的遞迴,比如10會9和8 9會有8和7 這時8已經出現了兩次 而相面更小的數字會有更多的重複,那麼我們把這些資料儲存下來,方便呼叫不是可以節省開銷?

private int[] memo;

    public int climbStairs(int n) {
        memo = new int[n + 1];
        Arrays.fill(memo, -1);
        return getClimbStairs(n);
    }

    private int getClimbStairs(int n) {
        if (n == 1) {
            return 1;
        }

        if (n == 2) {
            return 2;
        }

        if (memo[n] == -1) {
            memo[n] = getClimbStairs(n - 1) + getClimbStairs(n - 2);
        }
        return memo[n];
    }

我們只需要宣告一個memo陣列,把遞迴中間過程的值儲存下來就好,可以大大的提高效率,這時候提交給leetcode已經可以獲得AC了,同時,也得到了這個題目的狀態轉移方程:memo[i] = memo[i - 1] + memo[i - 2],有了狀態轉移方程,我們就可以遞推的去解決問題。

public int climbStairs(int n) {
        int[] memo= new int[n + 1];
        Arrays.fill(memo, -1);
        
        memo[0] = 1;
        memo[1] = 1;
        
        for (int i = 2; i <= n; i++)
            memo[i] = memo[i - 1] + memo[i - 2];
        
        return memo[n];
    }

這道題目本身很簡答,但是遇到困難問題時,希望這種先從上往下記憶化搜尋在從下往上層層遞推的思路,可以幫到同學們。