[剑指offer刷题10 - I] - 爬楼梯、青蛙跳台阶

题目:[题目及部分解法来自力扣]

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

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

  2. 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

    答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。(0 <= n <= 100)


solution one:

使用动态规划来处理斐波那契数列问题,最好理解,同时也最好处理!

当只有一个台阶的时:1 (1种爬法

当只有二个台阶的时:1 + 1/ 2  (2种爬法)

当只有三个台阶的时:1 + 1 + 1/ 1 + 2 / 2 + 1 (3种爬法)

当只有四个台阶的时:1 + 1 + 1 + 1 / 1 + 2 + 1 / 1 + 1 + 2 / 2 + 1 + 1/ 2 + 2 (5种爬法)

有木有像斐波那契数列??没有自己再列出几个数哈哈哈。

们用 f(x) 表示爬到第 x 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子:

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

它意味着爬到第 x 级台阶的方案数是爬到第 x - 1级台阶的方案数和爬到第 x - 2级台阶的方案数的和。

(注意,题目1和题目2只是n的起始值不,一个从0开始,一个从1开始!)

    //青蛙跳台阶
    public final int MOD = 1000000007;
    public int numWays(int n) {
        if(n < 2) return 1;
        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]) % MOD;
        }
        return dp[n];
    }

    //爬楼梯
    public final int MOD = 1000000007;

    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];

    }

solution two:

这里给出非递归方法破解以上题目的方法,只是起始值不同。

    // 青蛙跳台阶(0 <= n <= 100)
    public int numWays(int n) {
        int a = 1, b = 1, sum;
        for(int i = 0; i < n; i++){
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
        }
        return a;
    }

    
    // 爬楼梯(1 <= n <= 45)
     public int climbStairs(int n) {
        int p = 0, q = 0, r = 1;
        for (int i = 1; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }

solution three:

对于从1开始的斐波那契数列,官方给出了一个通项公式,利用通项公式求解。


//爬楼梯
public class Solution {
    public int climbStairs(int n) {
        double sqrt5 = Math.sqrt(5);
        double fibn = Math.pow((1 + sqrt5) / 2, n + 1) - Math.pow((1 - sqrt5) / 2, n + 1);
        return (int) Math.round(fibn / sqrt5);
    }
}

 solution four:

矩阵快速幂

 public int climbStairs(int n) {
        int[][] q = {{1, 1}, {1, 0}};
        int[][] res = pow(q, n);
        return res[0][0];
    }

    public int[][] pow(int[][] a, int n) {
        int[][] ret = {{1, 0}, {0, 1}};
        while (n > 0) {
            if ((n & 1) == 1) {
                ret = multiply(ret, a);
            }
            n >>= 1;
            a = multiply(a, a);
        }
        return ret;
    }

    public int[][] multiply(int[][] a, int[][] b) {
        int[][] c = new int[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
            }
        }
        return c;
    }

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值