题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
题解:
-
我们用 f(x)表示爬到第 x级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子: f(x)=f(x−1)+f(x−2)
-
它意味着爬到第 x 级台阶的方案数是爬到第 x−1 级台阶的方案数和爬到第 x−2 级台阶的方案数的和
-
很好理解,因为每次只能爬 1级或 2级,所以 f(x) 只能从 f(x−1和 f(x−2)转移过来,而这里要统计方案总数,我们就需要对这两项的贡献求和。
-
以上是动态规划的转移方程,下面我们来讨论边界条件。
-
我们是从第 0级开始爬的,所以从第 0级爬到第 0 级我们可以看作只有一种方案,即 f(0)=1;从第 0 级到第 1级也只有一种方案,即爬一级,f(1)=1。这两个作为边界条件就可以继续向后推导出第 n 级的正确结果
-
我们不妨写几项来验证一下,根据转移方程得到 f(2)=2,f(3)=3,f(4)=5 ,……,我们把这些情况都枚举出来,发现计算的结果是正确的。
-
我们不难通过转移方程和边界条件给出一个时间复杂度和空间复杂度都是 O(n) 的实现,但是由于这里的 f(x)只和 f(x−1)与 f(x−2)有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1)。
一开始想用递归来做一下是代码
public static int climbStairs(int n) { if(n<=1){ return 1; } return climbStairs(n-1)+climbStairs(n-2);}
很容易就实现了, 但是这样会有重复计算问题
这段代码的时间复杂度为O(2^n),因为每次递归调用都会分解为两个子问题,所以递归树的节点数为2^n。但是由于存在大量的重复计算,所以可以使用动态规划的方法进行优化,将中间结果保存起来,避免重复计算,从而将时间复杂度降低到O(n)。
优化后
public static int climbStairs3(int n){ int [] memo = new int[n+1]; return climbStairsHelper(n,memo);}private static int climbStairsHelper(int n, int[] memo) { if(n<=1){ return 1; } if(memo[n]>0){ return memo[n]; } memo[n] = climbStairsHelper(n-1,memo)+climbStairsHelper(n-2,memo); return memo[n] ;}
动态规划
public static int climbStairs2(int n) { if(n<=1){ return 1; } int p =0,q=0,r=1; for(int i=1;i<=n;i++){ p = q; q =r; r=p+q; } return r;}