爬楼梯:经典动态规划+递归法
爬楼梯
摘要
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解决方案
方法 1:暴力法
算法
在暴力法中,我们将会把所有可能爬的阶数进行组合,也就是 1 和 2 。而在每一步中我们都会继续调用
其中
class Solution {
public:
int climbStairs(int n) {
return climb_Stairs(0,n);
}
int climb_Stairs(int i ,int n){
if(i > n)return 0;
if(i == n)return 1;
return climb_Stairs(i+1,n) + climb_Stairs(i+2,n);
}
};
复杂度分析
-
时间复杂度:
。树形递归的大小为 。在
时的递归树将是这样的:
空间复杂度:
方法 2:记忆化递归
算法
在上一种方法中,我们计算每一步的结果时出现了冗余。另一种思路是,我们可以把每一步的结果存储在
在
class Solution {
public:
int climbStairs(int n) {
int memo[n+1];
return climb_Stairs(0,n,meno);
}
int climb_Stairs(int i ,int n,int meno[]){
if(i > n)return 0;
if(i == n)return 1;
if(meno[i] > 0)return meno[i];
meno = climb_Stairs(i+1,n,meno) + climb_Stairs(i+2,n,meno);
return meno[i];
}
};
复杂度分析
- 时间复杂度:
。树形递归的大小可以达到 。 - 空间复杂度:
。递归树的深度可以达到 。
方法 3:动态规划
算法
不难发现,这个问题可以被分解为一些包含最优子结构的子问题,即它的最优解可以从其子问题的最优解来有效地构建,我们可以使用动态规划来解决这一问题。
第 ii 阶可以由以下两种方法得到:
- 在第
阶后向上爬一阶。 - 在第
阶后向上爬 阶。
所以到达第 ii 阶的方法总数就是到第
令
代码:
class Solution {
public:
int climbStairs(int n) {
if(n <= 3)return n;
int dp[n+1];
dp[1] = 1;
dp[2] = 2;
for(int i = 3;i <= n; ++i)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
};
复杂度分析
- 时间复杂度:
,单循环到 。 - 空间复杂度:
。 数组用了 的空间。
方法 4: 斐波那契数
算法
在上述方法中,我们使用
现在我们必须找出以
class Solution {
public:
int climbStairs(int n) {
if (n == 1) {
return 1;
}
int first = 1;
int second = 2;
for (int i = 3; i <= n; i++) {
int third = first + second;
first = second;
second = third;
}
return second;
}
};
复杂度分析
- 时间复杂度:
。单循环到 ,需要计算第 个斐波那契数。 - 空间复杂度:
。使用常量级空间。
方法 5: Binets 方法
算法
这里有一种有趣的解法,它使用矩阵乘法来得到第 nn 个斐波那契数。矩阵形式如下:
我们需要为我们的问题做的唯一改动就是将斐波那契数列的初始项修改为 2 和 1 来代替原来的 1 和 0 。或者,另一种方法是使用相同的初始矩阵
public class Solution {
public int climbStairs(int n) {
int[][] q = {{1, 1}, {1, 0}};
int[][] res = pow(q, n);
return res[0][0];
}
public int[][] pow(int[][] a, int n) {
int[][] ret = {{1, 0}, {0, 1}};
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
}
public int[][] multiply(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
}
复杂度分析
- 时间复杂度:
。遍历 位。 - 空间复杂度:
。使用常量级空间。
这里的时间复杂度证明比较复杂,这里就不讨论了
方法 6: 斐波那契公式
算法
我们可以使用这一公式来找出第
class Solution {
public:
int climbStairs(int n) {
double sqrt5=sqrt(5);
double fibn=pow((1+sqrt5)/2,n+1)-pow((1-sqrt5)/2,n+1);
return (int)(fibn/sqrt5);
}
};
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库
· 5. Nginx 负载均衡配置案例(附有详细截图说明++)