文章目录
剑指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;
}
}
解法二:循环方式
由于递归存在重复计算的问题,故我们考虑改用循环的方法,只是本题起点是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 ;
}
}
解法三:动态规划
青蛙跳台阶其实也是一个典型的动态规划
的例题,用dp[i]
表示跳到第i
个台阶的跳法
状态转移:dp[i] = dp[i-1]+dp[i-2]
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];
}
}
青蛙变态台阶与矩形覆盖(剑指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];
}
}
矩形覆盖
我们可以用 2×1
的小矩形横着或者竖着去覆盖更大的矩形。请问 n
个 2×1
的小矩形无重叠地覆盖 2×n
的大矩形,总共有多少种方法?
解题思路
要覆盖 2×n
的大矩形,有两种选择:竖着放或者横着放。
- 当竖着放的时候,如下图所示,即先覆盖
2×1
的矩形,再覆盖2×(n-1)
的矩形。
- 当横着放的时候,如下图所示,当
2×1
的小矩形横着放在左上角的时候,左下角必须横着放一个2×1
的小矩形,再覆盖剩下的2×(n-2)
的矩形。
- 覆盖
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(n−1)+f(n−2)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) {
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]
}