一个还在爬楼梯的人

这道题是典型的动态规划的题目 第一种解法是使用递归方法,也是本题解题的核心思想。

如果要计算到达楼顶需要多少步,那么可以确定的是,到达楼顶前最后一步是要么走了一步,要么走了两步,将这两种情况的方案数加起来就是到达楼顶的总方案数,这样就可以得到动态规划转移方程 f(n) = f(n - 1) + f(n - 2) 然后再确定边界条件,当阶数为0或者1时,只有一种方案,而当阶数大于等于2时,可直接代入动态规划转移方程中

解法一:

class Solution {
    public int climbStairs(int n) {
        if(n < 2) return 1;
        return climbStairs(n - 1) + climbStairs(n - 2);
    }
}

然而这样做会超出题目时间限制,原因在于这样写会造成大量的重复计算。这种情况下可以用dp数组记录函数返回值完成记忆化搜索,于是有了以下解法

解法二:

class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 1]; // 定义动态规划dp数组
        return dfs(n, dp);
    }

    private int dfs(int n, int[] dp){
        if(n < 2) return 1;
        if(dp[n] != 0) return dp[n]; // 若dp数组中已经存储了该函数的返回值,直接返回其值
        return dp[n] = dfs(n - 1, dp) + dfs(n - 2, dp); // 否则计算该函数的返回值,并存储到dp中以避免重复计算
    }
}

这道题还可以换个思路,dp数组中的元素对应递归函数的返回值,可以得到 dp[n] = dp[n - 1] + dp[n - 2] 这就有点类似斐波拉契数列的思想了,那么直接计算dp数组的值就可以省去递归调用的开销

解法三:

class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

由于这道题只需要得到最终到达楼顶的方法数,也就是说我们只需要得到dp数组中最大下标的值,而我们去存储其他元素的值只是为了计算。观察以上代码在计算过程中,一旦dp[i]计算出来,那么dp[i - 2]以及之前的元素就不会再被用到了,也就是说只需要两个变量不断计算并更新自身的值就可以实现 这种思想被称作滚动数组思想,简单来说就是在dp方程中找到一种关系,可以用新的数据不断覆盖旧的数据来减少空间的使用

解法四:

class Solution {
    // 滚动数组
    public int climbStairs(int n) {
        int first = 1; // first用来指second前一个元素,初始指向dp[0]
				int second = 1; // second用来指first下一个元素,初始指向dp[1]
        // 循环次数表示向后滚动的次数
        for(int i = 1; i < n; i++){
            int temp = second;
            second = first + second; // second滚动到下一个元素的值
            first = temp; // first滚动到下一个元素的值
        }
        return second;
    }
}