青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

首先我们考虑最简单的情况。如果只有1级台阶,那显然只有一种跳法。如果有两级台阶,那就有两种跳法:一种是分两次跳,每次跳1级;另一种就是一次跳2级。

接着我们再谈论一般情况。我们把n级台阶时的跳法看成n的函数,记为f(n)。当n>2 时,第一次跳的时候就有两种不同的选择:一是第一次只跳1 级,此时跳法数目等于后面剩下的n-1 级台阶的跳法数目,即为f(n-1);二是第一次跳2 级,此时跳法数目等于后面剩下的n-2 级台阶的跳法数目,即为f(n-2)。
因此n 级台阶时的不同跳法的总数f(n) = f(n-1) + f(n-2)。

我们把上面的分析用一个公式总结如下:
f(n) =  1  (n=1)
f(n) =  2  (n=2)
f(n) = f(n-1) + (f-2)  (n>2)
分析到这里,我们不难看出这实际上是斐波那契数列(Fibonacci )了。

通常我们求解斐波那契数列的方式如下:

第一种方法:

public class Solution {

    public static void main(String[] args) {
        Long begintime = System.nanoTime();
        JumpFloor(10);
        Long endtime = System.nanoTime();
        System.out.println("用时:"+(endtime-begintime)+"ns");
    }

    public static int JumpFloor(int target) {
        if(target<1){
            return 0;
        }
        if(target==1){
            return 1;
        }
        if(target==2){
            return 2;
        }
        return JumpFloor(target-1)+JumpFloor(target-2);
    }

}

程序运行结果:

但是这种解法并不是最优的解决方法。因为这种递归的方式会产生很严重的效率问题。

我们以求解f(10)为例来分析递归的求解过程。向求得f(10),需要先求得f(9)和f(8)。同样,想求得f(9),需要先求得f(8)和f(7)……我们可以用树形结构来表示这种依赖关系,如下图所示:

我们可以发现,在这棵树中有很多节点都是重复的,而且重复的节点数会随着n的增大而急剧增加,这意味着计算量会随着n的增大而急剧增大。事实上,由于斐波那契数列是指数增长,所以该递归算法的时间复杂度也是以n的指数的方式递增的,即O(2^n)。

上述递归算法之所以慢,是因为重复的计算太多,我们只要想办法避免重复计算就行了。比如我们可以把已经得到的数列中间项保存起来,在下次需要计算的时候我们先查找一下,如果前面已经计算过就不用再重复计算了。

我们可以使用HashMap保存中间值。先创建一个哈希表,每次把不同参数的计算结果存入哈希。当遇到相同参数时,再从哈希表里取出,就不用重复计算了。

这种暂存结果的方式有一个很贴切的名字:[备忘录算法]。

第二种方法:备忘录算法的代码如下:

import java.util.HashMap;

public class Solution {

    public static void main(String[] args) {
        Long begintime = System.nanoTime();
        int result = JumpFloor(10);
        Long endtime = System.nanoTime();
        System.out.println("result="+result+";用时:"+(endtime-begintime)+"ns");
    }

    public static int JumpFloor(int target) {
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        if(target<1){
            return 0;
        }
        if(target==1){
            return 1;
        }
        if(target==2){
            return 2;
        }
        if(map.containsKey(target)){
            return map.get(target);
        }else{
            int value = JumpFloor(target-1)+JumpFloor(target-2);
            map.put(target,value);
            return value;
        }
    }

}

程序运行结果:

在以上代码中,集合map是一个备忘录。当每次需要计算F(N)的时候,会首先从map中寻找匹配元素。如果map中存在,就直接返回结果,如果map中不存在,就计算出结果,存入备忘录中。

从F(1)到F(N)共有n个不同的输入,在哈希表立存了n-2个结果,所以该算法的时间复杂度和空间复杂度都是O(n)。

虽然该算法的时间复杂度缩小了,但是却使用了额外的对象HashMap存储数据,提升了空间复杂度,而且HashMap存取数据的过程都需要时间。有没有一种算法,空间复杂度又小,同时又不用占用额外的存储空间呢?

上面的第一种方法是自顶向下计算。更简单的方法是自底向上计算。首先根据f(1)和f(2)算出f(3),再根据f(2)和f(3)算出f(4)……以此类推就可以算出第n项了。这种思路的时间复杂度是O(n),空间复杂度O(1),正好符合我们的需求

第三种方法代码如下:

public class Solution {

    public static void main(String[] args) {
        Long begintime = System.nanoTime();
        int result = JumpFloor(10);
        Long endtime = System.nanoTime();
        System.out.println("result="+result+";用时:"+(endtime-begintime)+"ns");
    }

    public static int JumpFloor(int target) {
        if(target<1){
            return 0;
        }
        if(target==1){
            return 1;
        }
        if(target==2){
            return 2;
        }
        int a = 1;
        int b = 2;
        int temp = 0;
        for(int i=3; i<=target; i++){
            temp = a + b;
            a = b;
            b = temp;
        }
        return temp;
    }

}

程序执行结果:

除此之外,还可以使用动态规划解决这个问题。

动态规划中包含三个重要的概念:[最优子结构],[边界],[状态转移公式]。

刚才我们分析出F(10) = F(9) + F(8),因此F(9)和F(8)是F(10)的[最优子结构]。

当只有一级台阶或两级台阶时,我们可以直接得出结果,无需继续简化。我们称F(1)和F(2)是问题的[边界]。如果一个问题没有边界,将永远无法得到有限的结果。

F(n) = F(n-1) + F(n-2)是阶段与阶段之间的[状态转移方程],这是动态规划的核心,决定了问题的每一个阶段和下一个阶段的关系。在动态规划中,通常用数组表示状态转移方程。

分析完问题我们就可以动手写代码了。

第四种方法动态规划解法如下:

public class Solution {

    public static void main(String[] args) {
        Long begintime = System.nanoTime();
        int result = JumpFloor(10);
        Long endtime = System.nanoTime();
        System.out.println("result="+result+";用时:"+(endtime-begintime)+"ns");
    }

    public static int JumpFloor(int target) {
        //第一次走1台阶,剩下n-1阶,第一次走2台阶,剩下n-2阶
        //所以状态转移方程为 dp[i] = dp[i-1] +dp[i-2];
        int[] dp = new int[target+1];
        dp[0] = dp[1] = 1;
        for (int i=2; i<=target; ++i) {
            dp[i] = (dp[i-1]+dp[i-2]);
        }
        return dp[target];
    }

}

程序运行结果:

该算法的时间复杂度也是O(n) 。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值