JS动态规划——青蛙跳台阶问题

一只青蛙一次可以跳上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,此操作与最终返回前取余等价。

简书同文欢迎关注 JS动态规划——青蛙跳台阶问题

猜你喜欢

转载自blog.csdn.net/qq_38970408/article/details/122240065