0
点赞
收藏
分享

微信扫一扫

3643、爬楼梯

假设你正在爬楼梯。需要 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 阶

import java.util.HashMap;

import java.util.Map;

/**

 * 爬楼梯

 * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

 * @author wen.lei@brgroup.com

 *

 * 2022-1-4

 */

public class ClimbingStairs {

     

    /**

     * 方法1:最简单的递归(leecode上超时了)

     * f(x)=f(x-1)+f(x-2)

     * @param n

     * @return

     */

    public int climbStairs(int n) {

        if(n<=0) {

            return 0;

        }

        else if(n==1) {

            return 1;

        }else if(n==2) {

            return 2;

        }else {

            return climbStairs(n-1)+climbStairs(n-2);

        }

    }

     

    //看网友答案,递归时可以加点缓存。还以为可行,其实不行

    //因为是从后向前倒退的,因为前面几个没有计算,继续倒退

    //从小到大累加,才是更快的办法

    Map<Integer,Integer> cache=new HashMap<Integer,Integer>();

    public int climbStairsCache(int n) {

        if(n<=0) {

            return 0;

        }

        else if(n==1) {

            return 1;

        }else if(n==2) {

            return 2;

        }else {

            if(cache.get(n)!=null) {

                return cache.get(n);

            }

            int nValue= climbStairs(n-1)+climbStairs(n-2);

            cache.put(n, nValue);

            return nValue;

        }

    }

     

    /**

     * 方法2:从小到大累加,充分利用每次的计算(滚动数组)

     * f(x)=f(x-1)+f(x-2),f(1)=1,f(2)=2;f(0)=0不太正常

     * @param n

     * @return

     */

    public int climbStairsDp(int n) {

        if(n<=0) {

            return 0;

        }

        else if(n==1) {

            return 1;

        }else if(n==2) {

            return 2;

        }

        //0不用,感觉没有意义

        int[] array = new int[n+1];

        array[1]=1;

        array[2]=2;

        for(int index=3;index<=n;index++) {

            array[index]=array[index-1]+array[index-2];

        }

        return array[n];

    }

     

    /**

     * 方法3:从小到大累加,充分利用每次的计算(不用数组,空间优化)

     * f(x)=f(x-1)+f(x-2),f(1)=1,f(2)=2;f(0)=0不太正常

     * @param n

     * @return

     */

    public int climbStairsDpTmpVariable(int n) {

        if(n<=0) {

            return 0;

        }

        else if(n==1) {

            return 1;

        }else if(n==2) {

            return 2;

        }

        //0不用,感觉没有意义

        int dp1=1;

        int dp2=2;

        int dpIndex=0;

        for(int index=3;index<=n;index++) {

            //f(x)=f(x-1)+f(x-2)

            dpIndex=dp1+dp2;

            //向前滚动,复用变量

            dp1=dp2;

            dp2=dpIndex;

        }

        return dpIndex;

    }

     

    //方法4 矩阵快速幂,方法5 通项公式(纯数学了,艹)

}

package cn.fansunion.leecode;

import org.junit.Assert;

import org.junit.Test;

import cn.fansunion.leecode.todo.ClimbingStairs;

import cn.hutool.core.date.StopWatch;

/**

 * @author wen.lei@brgroup.com

 *

 * 2022-1-11

 */

public class ClimbingStairsTest {

    @Test

    public void test() {

        ClimbingStairs cs = new ClimbingStairs();

        final int actual45 = 1836311903;

        StopWatch sw45 = new StopWatch();

        sw45.start();

        Assert.assertEquals(cs.climbStairs(45), actual45);

        sw45.stop();

        System.out.println("45:"+sw45.getTotalTimeMillis()+"ms");

        StopWatch sw37 = new StopWatch();

        sw37.start();

        Assert.assertEquals(cs.climbStairs(37), 39088169);

        sw37.stop();

        System.out.println("37:"+sw37.getTotalTimeMillis()+"ms");

    }

     

    @Test

    public void testDp() {

        ClimbingStairs cs = new ClimbingStairs();

        final int actual45 = 1836311903;

        StopWatch sw45 = new StopWatch();

        sw45.start();

        Assert.assertEquals(cs.climbStairsDp(45), actual45);

        sw45.stop();

        System.out.println("45:"+sw45.getTotalTimeNanos()+"ns");

        StopWatch sw37 = new StopWatch();

        sw37.start();

        Assert.assertEquals(cs.climbStairsDp(37), 39088169);

        sw37.stop();

        System.out.println("37:"+sw37.getTotalTimeNanos()+"ns");

    }

     

    @Test

    public void climbStairsDpTmpVariable() {

        ClimbingStairs cs = new ClimbingStairs();

        final int actual45 = 1836311903;

        StopWatch sw45 = new StopWatch();

        sw45.start();

        Assert.assertEquals(cs.climbStairsDpTmpVariable(45), actual45);

        sw45.stop();

        System.out.println("45:"+sw45.getTotalTimeNanos()+"ns");

        StopWatch sw37 = new StopWatch();

        sw37.start();

        Assert.assertEquals(cs.climbStairsDpTmpVariable(37), 39088169);

        sw37.stop();

        System.out.println("37:"+sw37.getTotalTimeNanos()+"ns");

    }

     

    @Test

    public void testCache() {

        ClimbingStairs cs = new ClimbingStairs();

        final int actual45 = 1836311903;

        StopWatch sw45 = new StopWatch();

        sw45.start();

        Assert.assertEquals(cs.climbStairsCache(45), actual45);

        sw45.stop();

        System.out.println("45Cache:"+sw45.getTotalTimeMillis());

        StopWatch sw37 = new StopWatch();

        sw37.start();

        Assert.assertEquals(cs.climbStairsCache(37), 39088169);

        sw37.stop();

        System.out.println("37Cache:"+sw37.getTotalTimeMillis());

    }

}

总结
这里形成的数列正好是斐波那契数列,答案要求的 f(n)f(n) 即是斐波那契数列的第 nn 项(下标从 00 开始)。我们来总结一下斐波那契数列第 nn 项的求解方法:

nn 比较小的时候,可以直接使用过递归法求解,不做任何记忆化操作,时间复杂度是 O(2^n)O(2
n
),存在很多冗余计算。
一般情况下,我们使用「记忆化搜索」或者「迭代」的方法,实现这个转移方程,时间复杂度和空间复杂度都可以做到 O(n)O(n)。
为了优化空间复杂度,我们可以不用保存 f(x - 2)f(x−2) 之前的项,我们只用三个变量来维护 f(x)f(x)、f(x - 1)f(x−1) 和 f(x - 2)f(x−2),你可以理解成是把「滚动数组思想」应用在了动态规划中,也可以理解成是一种递推,这样把空间复杂度优化到了 O(1)O(1)。
随着 nn 的不断增大 O(n)O(n) 可能已经不能满足我们的需要了,我们可以用「矩阵快速幂」的方法把算法加速到 O(\log n)O(logn)。
我们也可以把 nn 代入斐波那契数列的通项公式计算结果,但是如果我们用浮点数计算来实现,可能会产生精度误差。

举报

相关推荐

0 条评论