爬楼梯问题
参考文章:
有了四步解题法模板,再也不害怕动态规划!
DP动态规划
一般解决动态规划问题,分为四个步骤,分别是
- 问题拆解,找到问题之间的具体联系
- 状态定义
- 递推方程推导
- 实现
-
“1+1+1+1+1+1+1+1” 得出答案是 8,那么如何快速计算 “1+ 1+1+1+1+1+1+1+1”,我们首先可以对这个大的问题进行拆解,这里我说的大问题是 9 个 1 相加,这个问题可以拆解成 1 + “8 个 1 相加的答案”,8 个 1 相加继续拆,可以拆解成 1 + “7 个 1 相加的答案”,… 1 + “0 个 1 相加的答案”,到这里,第一个步骤 已经完成。
-
状态定义: 其实是需要思考在解决一个问题的时候我们做了什么事情,然后得出了什么样的答案,对于这个问题,当前问题的答案就是当前的状态,基于上面的问题拆解,你可以发现两个相邻的问题的联系其实是 后一个问题的答案 = 前一个问题的答案 + 1,这里,状态的每次变化就是 +1。
-
定义好了状态,递推方程就变得非常简单,就是 dp[i] = dp[i - 1] + 1,这里的 dp[i] 记录的是当前问题的答案,也就是当前的状态,dp[i - 1] 记录的是之前相邻的问题的答案,也就是之前的状态,它们之间通过 +1 来实现状态的变更。
-
最后一步就是实现了,有了状态表示和递推方程,实现这一步上需要重点考虑的其实是初始化,就是用什么样的数据结构,根据问题的要求需要做那些初始值的设定。
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
算法设计
问题拆解:
我们到达第 n 个楼梯可以从第 n – 1 个楼梯和第 n – 2 个楼梯到达,因此第 n 个问题可以拆解成第 n – 1 个问题和第 n – 2 个问题,第 n – 1 个问题和第 n – 2 个问题又可以继续往下拆,直到第 0 个问题,也就是第 0 个楼梯 (起点)
状态定义
“问题拆解” 中已经提到了,第 n 个楼梯会和第 n – 1 和第 n – 2 个楼梯有关联,那么具体的联系是什么呢?你可以这样思考,第 n – 1 个问题里面的答案其实是从起点到达第 n – 1 个楼梯的路径总数,n – 2 同理,从第 n – 1 个楼梯可以到达第 n 个楼梯,从第 n – 2 也可以,并且路径没有重复,因此我们可以把第 i 个状态定义为 “从起点到达第 i 个楼梯的路径总数”,状态之间的联系其实是相加的关系。
递推方程
“状态定义” 中我们已经定义好了状态,也知道第 i 个状态可以由第 i – 1 个状态和第 i – 2 个状态通过相加得到,因此递推方程就出来了 dp[i] = dp[i - 1] + dp[i - 2]
实现
你其实可以从递推方程看到,我们需要有一个初始值来方便我们计算,起始位置不需要移动 dp[0] = 0,第 1 层楼梯只能从起始位置到达,因此 dp[1] = 1,第 2 层楼梯可以从起始位置和第 1 层楼梯到达,因此 dp[2] = 2,有了这些初始值,后面就可以通过这几个初始值进行递推得到。
描述算法
伪代码描述
IF 1 == n
RETURN 1
P[0] = 0
P[1] = 1
P[2] = 2
FOR i = 3 TO n
p[i] = p[i - 1] + p[i - 2]
RETURN P[n]
复杂性分析
时间复杂度
O(n)
空间复杂度
O(n)
代码实现
#include <iostream>
int climbStairs(int n);
int main()
{
int n = 9;
std::cout << climbStairs(n) << std::endl;
return 0;
}
int climbStairs(int n)
{
if (1 == n)
{
return 1;
}
int* c = new int[n+1];
c[0] = 0;
c[1] = 1;
c[2] = 2;
for (int i = 3; i <= n; i++)
{
c[i] = c[i - 1] + c[i - 2];
}
return c[n];
}
运行结果