一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
- 示例 1:
输入:n = 2
输出:2- 示例 2:
输入:n = 7
输出:21- 示例 3:
输入:n = 0
输出:1
提示:0 <= n <= 100
- 解题思路:
此类求 多少种可能性 的题目一般都有 递推性质 ,即 f(n)f(n) 和 f(n-1)f(n−1)…f(1)f(1) 之间是有联系的。
设跳上 n 级台阶有 f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。
当为 1 级台阶: 剩 n-1 个台阶,此情况共有 f(n-1) 种跳法;
当为 2 级台阶: 剩 n-2 个台阶,此情况共有 f(n-2) 种跳法。
f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2) ,以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第 n 项的值 。
青蛙跳台阶问题: f(0)=1 , f(1)=1 , f(2)=2,;
斐波那契数列问题: f(0)=0 , f(1)=1 , f(2)=1 。
第n阶的数量由前两阶的数量相加而来,故用动态规划。
arr[i]表示第i阶有arr[i]种方法
递推公式:arr[i] = arr[i - 1] + arr[i - 2]
arr数组初始化:arr = [null, 1, 2],arr[0]没有意义,从i=3开始循环
遍历顺序:从前往后
代码:动态规划
/**
* @param {number} n
* @return {number}
*/
var numWays = function(n) {
//动态规划
if(n===0){
return 1;
}
const arr=[null,1,2];
for(let i=3;i<=n;i++){
arr[i]=(arr[i-1]+arr[i-2])%1000000007;
}
return arr[n];
};
总结
斐波那契数列的定义是 f(n + 1) = f(n) + f(n - 1),生成第 n 项的做法有以下几种:
- 递归法:
原理: 把 f(n) 问题的计算拆分成 f(n-1)和 f(n-2) 两个子问题的计算,并递归,以 f(0) 和 f(1) 为终止条件。
缺点: 大量重复的递归计算,例如 f(n)和 f(n−1) 两者向下递归都需要计算 f(n - 2) 的值。- 记忆化递归法:
原理: 在递归法的基础上,新建一个长度为 n 的数组,用于在递归时存储 f(0)至 f(n) 的数字值,重复遇到某数字时则直接从数组取用,避免了重复的递归计算。
缺点: 记忆化存储的数组需要使用 O(N) 的额外空间。- 动态规划:
原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1),f(n+1)=f(n)+f(n−1) 为转移方程。
从计算效率、空间复杂度上看,动态规划是本题的最佳解法。
空间复杂度优化
若新建长度为 n 的 dp 列表,则空间复杂度为 (N) 。
- 由于 dp 列表第 i 项只与第 i−1 和第 i−2 项有关,因此只需要初始化三个整形变量 sum, a, b ,利用辅助变量 sum 使 a, b 两数字交替前进即可 (具体实现见代码)
- 因为节省了 dp 列表空间,因此空间复杂度降至 O(1)
循环求余法:
- 大数越界: 随着 n 增大, f(n) 会超过 Int32 甚至 Int64 的取值范围,导致最终的返回值错误。
- 求余运算规则: 设正整数 x, y, p,求余符号为 ⊙ ,则有 (x + y) ⊙ p = =(x⊙p+y⊙p)⊙p 。
解析: 根据以上规则,可推出 f(n) ⊙ p = [f(n-1)⊙ p + f(n-2) ⊙ p] ⊙p ,从而可以在循环过程中每次计算 sum = (a + b) ⊙1000000007,此操作与最终返回前取余等价。