题干
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
数据范围:0<= n <=40
要求:时间复杂度:O(n) ,空间复杂度O(1)。
解题思路
首先,这是一道经典的DP(动态规划)问题,动态规划是通过组合子问题的解而解决整个问题的。动态规划通常应用于最优化问题
。其解法类似于我们在数学中学过的归纳法:
1. 描述最优解的结构
2. 递归定义最优解的值
3. 按自底向上的方式计算最优解的值
- 摘自《算法导论》
OK,理论讲完了,我们来分析一下这个青蛙跳台阶的问题,青蛙要跳上n级的台阶有两种情况,情况1是青蛙先跳到了(n-2)级台阶,然后又跳了2级;情况2是青蛙先跳到了(n-1)级台阶,然后又跳了1级。那么这个问题就拆解为了青蛙跳到n-2级台阶有几种跳法和青蛙跳到n-1级台阶有几种跳法两个子问题,即f(n)=f(n-1) + f(n-2),以此递归,直到f(2)和f(1),这两个的解我们是已知的。
显然,我们可以用递归的方式来写很快实现此功能,见示例代码1,但是递归的解法显然不满足题干里时间复杂度O(n)的要求,显然很多值我们需要重复计算,比如计算f(5)的解,我们需要先计算f(4)和f(3)的解,计算f(4)的时候又需要计算f(3)和f(2)的解,那么f(3)就被计算了两次。当然,聪明的你肯定能想出使用一个数组或map来保存第一次得到的结果,下次需要的时候直接就取出来用的方法,可以避免重复计算,见示例代码2,可这又违背了题干中空间复杂度O(1)的要求。
那怎么办呢?上面说了,我们可以使用自底向上的计算方式,即循环遍历,先计算出f(3),然后计算f(4),以此类推,不需要额外的存储空间。这一解法让我不禁想起金庸小说里的一招轻功“武当梯云纵”,就是不断的左脚踩右脚,哈哈~~
示例代码
- 常规递归解法
public class Solution {
public int jumpFloor(int target) {
if(target == 1) return 1;
if(target == 2) return 2;
return jumpFloor(target -1) + jumpFloor(target -2);
}
}
- 保存中间结果
public class Solution {
Map<Integer,Integer> tmpMap = new HashMap<>();
public int jumpFloor(int target) {
if(tmpMap.containsKey(target)) return tmpMap.get(target);
if(target == 1) return 1;
if(target == 2) return 2;
int result = jumpFloor(target -1) + jumpFloor(target -2);
tmpMap.put(target,result);
return result;
}
}
- 最终解法
public class Solution {
public int jumpFloor(int target) {
int a=1,b=1,c=0;
for(int i=2;i<=target;i++){
c = a + b;
a = b;
b = c;
}
return c;
}
}
延伸拓展
相关问题变种,总之就是魔鬼青蛙各种跳:
- 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
- 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上m级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
看看你能不能找到那个递归公式吧~~