"斐波那契,动态规划开始的地方"
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情
70. 爬楼梯 题目描述:假设你正在爬楼梯。需要 阶你才能到达楼顶。每次你可以爬 或 个台阶。你有多少种不同的方法可以爬到楼顶呢?输入 = 7,输出 。
中规中矩的动态规划
动态规划问题最难的一点就是找到问题的 最优子结构,即如何定义 状态数组和 状态转移方程,而最简单的动态规划问题的最优子结构就写在问题的题面上。
对于 级台阶,如果只知道爬到第 级台阶的不同方法,记做 ,再一次性爬一阶即可到达顶点;如果只知道爬到第 级台阶的不同方法,记做 ,再一次性爬两阶即可到达顶点,所以爬到第 级台阶共有 种方法,。
1、确定 dp 状态数组
定义 为爬上 级台阶的不同方法数,其中 ;
NOTE: 的定义域虽为 ,但是数组是从 开始的,所以初始化 的长度应该是 。
2、确定 dp 状态方程
的状态转移方程:
3、确定 dp 初始状态
观察状态方法,有 和 两项,故我们需要定义,
-
当 时, 级台阶只能向上爬一步,即 ;
-
当 时, 级台阶可以一步一步爬,也可以一次性爬两步,即 。
NOTE: 这里无需纠结 级台阶,即 还是 的问题,这本身是一个无意义的点,而且循环遍历时也不会使用这个值,忽略即可。
4、确定遍历顺序
由于 和 均已定义完毕,故遍历顺序从 到 。
5、确定最终返回值
回归状态定义,爬上 级台阶的不同方法数为 。
6、代码示例
/**
* 空间复杂度 O(n)
* 时间复杂度 O(n)
*/
function climbStairs(n: number): number {
const dp = new Array(n + 1).fill(0);
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <=n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
};
状态压缩, 仅与 和 相关,所以可以将空间复杂度 压缩成 。
/**
* 空间复杂度 O(1)
* 时间复杂度 O(n)
*/
function climbStairs(n: number): number {
if (n <= 2) return n;
let dp1 = 1, dp2 = 2;
for (let i = 3; i <= n; i++) {
let temp = dp2;
dp2 = dp1 + dp2;
dp1 = temp;
}
return dp2;
};
思维提升-斐波那契通项公式
细想爬楼梯问题,类似于斐波那契数列,而斐波那契数列的通项公式为,
-
斐波那契数列展开:1、1、2、3、5、 8、13、21、34...
-
爬楼梯的数列展开:1、2、3、5、8、13、21、34、55...
发现规律了吧~~所以对于爬楼梯问题的通项公式为,
代码示例
/**
* 空间复杂度O(log(n))
* 时间复杂度O(log(n))
*/
function climbStairs(n: number): number {
if (n <= 2) {
return n;
}
const sqrt5 = Math.sqrt(5);
const fibN = Math.pow((1 + sqrt5) / 2, n + 1) - Math.pow((1 - sqrt5) / 2, n + 1);
return Math.round(fibN / sqrt5);
};
💥 NOTE,JS中的
Math.pow函数空间复杂度与时间复杂度大致为 ,具体与引擎实现有关,不做深入分析。