题目描述
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
解决方案
方案一:普通递归
每次只能爬一个或两个台阶,所以:
- 当台阶数为1时,只有一种爬行方法,
- 当台阶数为2时,有每次爬一个台阶和一次性爬两个台阶的方法,
- 当台阶数为3时,可以在已爬行1个台阶的基础上爬行两次一个台阶,或者一次性爬行两个台阶,也可以在爬行2个台阶的基础上爬行一个台阶,共3种方法。
- 以此类推,有递推公式:F(n) = F(n-1) + F(n-2);
代码实现:
class Solution {
public:
int climbStairs(int n) {
if(n==1){
return 1;
}
if(n==2){
return 2;
}
return climbStairs(n-1)+climbStairs(n-2);
}
};
存在的问题:
有太多重复的计算,在输入较大时,会导致运行超时。
方案二:记忆化递归
在方案一的基础上,添加记忆功能,将已经计算过的中间结果存如mem[n+1]数组,每次递归时先判断是否已经计算过,即判断mem[n]的值是否大于0,如果大于则返回该数组的值,否则说明是第一次计算,则去判断是否等于1或2,是则返回相应值,否则继续递归分别传入n-1和n-2以及数组。
代码实现:
class Solution {
public:
int climbStairs(int n) {
int memo[n+1];
memset(memo,0,sizeof(memo));
return climbStairsMemo(n,memo);
}
int climbStairsMemo(int n,int memo[]){
//已经计算过
if(memo[n]>0){
return memo[n];
}
if(n==1){
memo[n]=1;
}else if(n==2){
memo[n]=2;
}else{
//继续递归
memo[n] = climbStairsMemo(n-1,memo) + climbStairsMemo(n-2,memo);
}
return memo[n];
}
};
注意:数组定义后需要初始化,否则会导致初始值为任意的垃圾数字。
方案三:动态规划
把爬到每个台阶的方法数视为一个状态,则需要记录n个状态,我们用dp数组保存这些状态,从1到n依次更新,更新时每次只用到前两个保存的数据。
代码实现:
class Solution {
public:
int climbStairs(int n) {
if(n==1){
return 1;
}
int dp[n+1];
memset(dp,0,sizeof(dp));
dp[1]=1;
dp[2]=2;
//从底层往上遍历
for(int i=3;i<=n;++i){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
总结心得
动态规划算法记录了子问题的解,避免了下次遇到相同的问题导致重复计算。重点是要找到状态转移方程,即子问题之间的关系