10.2 青蛙跳台阶问题与矩形覆盖问题

剑指offer 10-II 青蛙跳台阶问题 LeetCode70

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

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

示例 1:

输入: n = 2
输出: 2

示例 2:

输入: n = 7
输出: 21

示例 3:

输入: n = 0
输出: 1

提示:

0 <= n <= 100
注意:本题与主站 70 题相同:https://leetcode-cn.com/problems/climbing-stairs/

解法一:递归方式

虽然本题没有明说是斐波那契数列,但经过分析会发现其实就是斐波那契数列的应用。思路和斐波那契数列的递归完全一样,我将在本处引出很多其他应用。

思路:用递归的方法,例如,第一次跳有两种不同的选择
   1. 爬一个台阶,则此时爬法数目等于后面剩下n-1阶的爬法数目climbStairs(n-1)
   2. 爬两个台阶,则此时爬法数目等于后面剩下n-2阶的爬法数目climbStairs(n-1)
  总的爬法数目climbStairs(n) = climbStairs(n-1)+climbStairs(n-2)
由上可知,这是斐波那契数列问题,该处用递归的话,有很多计算是重复的,耗时长

注:本题递归的方式提交的时候会超时,另外代码中用到了(a + b) % p = (a % p + b % p) % p

递归代码

class Solution {
    public int climbStairs(int n) {
        if(n == 1){
            return 1;
        }
        if(n == 2){
            return 2;
        }
        int num1 = climbStairs(n-1) % 1000000007;
        int num2 = climbStairs(n-2) % 1000000007;
        return (num1 + num2) % 1000000007;
    }
}

解法二:循环方式

Java代码
由于递归存在重复计算的问题,故我们考虑改用循环的方法,只是本题起点是1,2(第一步跳一个台阶或者两个台阶),而不是传统的0,1

class Solution {
    public int numWays(int n) {
        int a = 1;
        int b = 2;
        int c = 0;
        //注意此处是n-1,而不是n,因为前两个数a,b已经直接给出了,现在只要计算到n-2即可
        for(int i = 0;i < n-1;i++){
            c = (a + b) % 1000000007;
            a = b;
            b = c;
        }
        return a ;
    }
}

在这里插入图片描述
go代码

func numWays(n int) int {
    a := 1
    b := 2
    for i := 0; i < n - 1;i++ {
        c := (a + b) % 1000000007
        a,b = b,c
    }
    return a
}

解法三:动态规划

青蛙跳台阶其实也是一个典型的动态规划的例题,用dp[i]表示跳到第i个台阶的跳法
状态转移:dp[i] = dp[i-1]+dp[i-2]

Java代码

class Solution {
    public int numWays(int n) {
        //由于dp[n]表示的是跳到第n个台阶的跳法,所以数组长度应该是dp[n+1]
        //如果数组长度是n,则dp[n]将出现数组下标越界问题
        int[] dp = new int[n+1];
        if(n==0 || n == 1) return 1;
        if(n == 2) return 2;
        dp[1]=1;
        dp[2]=2;
        for(int i = 3;i <= n;i++){
            dp[i] = (dp[i-1] + dp[i-2]) % 1000000007;
        }
        return dp[n];
    }
}

在这里插入图片描述
go代码

func numWays(n int) int {
    if n == 0 || n == 1 { return 1 }
    //这里需要用切片,无法用数组,因为写成var dp [n + 1]int会报错,说定义数组时长度不是常量
    dp := make([]int, n + 1)
    dp[0],dp[1] = 1,1
    for i := 2 ; i <= n;i++ {
        dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007
    }
    return dp[n]
}

青蛙变态台阶与矩形覆盖(剑指offer第十题后面的扩展题)

青蛙变态跳台阶

一直青蛙一次可以跳上 1 级台阶,也可以跳上 2 级…它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
在这里插入图片描述

解题思路

与之前可以跳上一级台阶或者两阶台阶类似,不过是可以跳上任意阶台阶而已,思路仍然是用动态规划,用dp[i]表示跳上第i阶台阶的跳法种数。则dp[i]=dp[i-1]+dp[i-2]+···+dp[0]

Java代码

class Solution {
    public int numWays(int n) {
        //由于dp[n]表示的是跳到第n个台阶的跳法,所以数组长度应该是dp[n+1]
        //如果数组长度是n,则dp[n]将出现数组下标越界问题
        int[] dp = new int[n+1];
        if(n==0 || n == 1) return 1;
        if(n == 2) return 2;
        dp[1]=1;
        dp[2]=2;
        for(int i = 3;i <= n;i++){
            for(int j = 0;j < i;j++ ){//dp[i]=dp[i-1]+dp[i-2]+···+dp[0]
                  dp[i] += dp[j];
            } 
        }
        return dp[n];
    }
}

go代码


  func numWays(n int) int {
      //由于dp[n]表示的是跳到第n个台阶的跳法,所以数组长度应该是dp[n+1]
      //如果数组长度是n,则dp[n]将出现数组下标越界问题
      dp := make([]int,n + 1)
      if(n == 0 || n == 1) return 1;
      if(n == 2) return 2;
      dp[1]=1;
      dp[2]=2;
      for i := 3;i <= n;i++ {
          for j := 0;j < i;j++ {//dp[i]=dp[i-1]+dp[i-2]+···+dp[0]
                dp[i] += dp[j];
          } 
      }
      return dp[n];
  }

矩形覆盖

我们可以用 2×1 的小矩形横着或者竖着去覆盖更大的矩形。请问 n2×1 的小矩形无重叠地覆盖 2×n 的大矩形,总共有多少种方法?
在这里插入图片描述

解题思路

要覆盖 2×n 的大矩形,有两种选择:竖着放或者横着放。

  1. 当竖着放的时候,如下图所示,即先覆盖 2×1 的矩形,再覆盖 2×(n-1) 的矩形。
    图1
  2. 当横着放的时候,如下图所示,当2×1的小矩形横着放在左上角的时候,左下角必须横着放一个2×1的小矩形,再覆盖剩下的 2×(n-2) 的矩形。
    在这里插入图片描述
  3. 覆盖 2×(n-1)2×(n-2) 的矩形可以看成子问题。

从而得出该问题的递推公式:
f ( x ) = { 1 n = 1 2 n = 2 f ( n − 1 ) + f ( n − 2 ) n > 2 f(x)=\left\{ \begin{aligned} 1 &&&& n=1\\ 2 &&&& n =2 \\ f(n-1)+f(n-2) &&&& n > 2 \end{aligned} \right. f(x)=12f(n1)+f(n2)n=1n=2n>2

斐波那契循环写法

java代码

public int RectCover(int n) {
    if (n <= 2)
        return n;
    int a = 1, b = 2;
    int result = 0;
    for (int i = 3; i <= n; i++) {
        result = a + b;
        a = b;
        b = result;
    }
    return result;
}
动态规划写法

dp[i]表示用 2×1 的小矩形覆盖2*i的大矩形共有多少种方法,则dp[n]表示的便是用 2×1 的小矩形覆盖2*n的大矩形共有多少种方法。
状态转移:dp[i] = dp[i - 1] + dp[i - 2]

public int RectCover(int n) {
	if (n == 0 || n == 1 || n == 2) return n;
    int[] dp = new int[n+1];
    //初始化dp
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 2;
    for(int i = 3;i <= n;i++){
    	dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n]
}

go写法和上面几乎一样,偷个懒,略

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值