文章目录
70. 爬楼梯
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
go
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
go
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
- 1 <= n <= 45
思路
本题大家如果没有接触过的话,会感觉比较难,多举几个例子,就可以发现其规律。
爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层,即因为一次只能爬一阶或者两阶台阶,那么爬到第n阶,肯定是从第n-1阶或者n-2阶爬上来的
。
我们来分析一下,动规五部曲:
定义一个一维数组来记录不同楼层的状态
1.确定dp数组以及下标的含义
dp[i]
: 爬到第i
层楼梯,有dp[i]
种方法
2.确定递推公式
如何可以推出dp[i]
呢?
从dp[i]
的定义可以看出,dp[i]
可以有两个方向推出来。
首先是dp[i - 1]
,上i-1
层楼梯,有dp[i - 1]
种方法,那么再一步跳一个台阶不就是dp[i]
了么。
还有就是dp[i - 2]
,上i-2
层楼梯,有dp[i - 2]
种方法,那么再一步跳两个台阶不就是dp[i]
了么。
那么dp[i]
就是 dp[i - 1]
与dp[i - 2]
之和!
所以dp[i] = dp[i - 1] + dp[i - 2]
。
在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
这体现出确定dp数组以及下标的含义的重要性!
3.dp数组如何初始化
再回顾一下dp[i]
的定义:爬到第i
层楼梯,有dp[i]
种方法。
那么i
为0
,dp[i]
应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。
例如强行安慰自己爬到第0
层,也有一种方法,什么都不做也就是一种方法即:dp[0] = 1
,相当于直接站在楼顶。
但总有点牵强的成分。
那还这么理解呢:我就认为爬到第0
层,方法就是0
啊,一步只能走一个台阶或者两个台阶,然而楼层是0
,直接站楼顶上了,就是不用方法,dp[0]
就应该是0
.
其实这么争论下去没有意义,大部分解释说dp[0]
应该为1
的理由其实是因为dp[0]=1
的话在递推的过程中,i
从2
开始遍历本题就能过,然后就往结果上靠去解释dp[0] = 1
。
从dp
数组定义的角度上来说,dp[0] = 0
也能说得通。
需要注意的是:题目中说了n是一个正整数,题目根本就没说n有为0的情况。
所以本题其实就不应该讨论dp[0]的初始化!
我相信dp[1] = 1,dp[2] = 2
,这个初始化大家应该都没有争议的。
所以我的原则是:不考虑dp[0]
如何初始化,只初始化dp[1] = 1,dp[2] = 2
,然后从i = 3
开始递推,这样才符合dp[i]
的定义。
4.确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2]
;中可以看出,遍历顺序一定是从前向后遍历的
5.举例推导dp数组
举例当n
为5
的时候,dp数组
应该是这样的
如果代码出问题了,就把dp数组
打印出来,看看究竟是不是和自己推导的一样。
此时大家应该发现了,这不就是斐波那契数列么!
唯一的区别是,没有讨论dp[0]
应该是什么,因为dp[0]
在本题没有意义!
以上五部分析完之后,Go
代码如下:
版本一
go
func climbStairs(n int) int {
// 因为下面直接对dp[1],dp[2]操作了,防止空指针
if n <= 2 {
return n
}
dp := make([]int,n + 1)
dp[1] = 1
dp[2] = 2
for i := 3;i <= n;i++ {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[n]
}
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
当然依然也可以,优化一下空间复杂度,代码如下:
版本二
go
func climbStairs(n int) int {
// 因为下面直接对dp[1],dp[2]操作了,防止空指针
if n <= 2 {
return n
}
dp := make([]int,3)
dp[1] = 1
dp[2] = 2
for i := 3;i <= n;i++ {
sum := dp[1] + dp[2]
dp[1] = dp[2]
dp[2] = sum
}
return dp[2]
}
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
后面将讲解的很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化,但我个人认为面试中能写出版本一就够了哈,清晰明了,如果面试官要求进一步优化空间的话,我们再去优化。
因为版本一才能体现出动规的思想精髓,递推的状态变化。
总结
这道题目和509.斐波那契数
题目基本是一样的,但是会发现本题相比509.斐波那契数
难多了,为什么呢?
关键是: 509.斐波那契数
题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。
而本题,就需要逐个分析了,大家现在应该初步感受出动规五部曲的重要性了。
简单题是用来掌握方法论的,例如昨天斐波那契的题目够简单了吧,但昨天和今天可以使用一套方法分析出来的,这就是方法论!
所以不要轻视简单题,那种凭感觉就刷过去了,其实和没掌握区别不大,只有掌握方法论并说清一二三,才能触类旁通,举一反三哈!