70. 爬楼梯

文章目录

70. 爬楼梯

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 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]种方法。

那么i0dp[i]应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。

例如强行安慰自己爬到第0层,也有一种方法,什么都不做也就是一种方法即:dp[0] = 1,相当于直接站在楼顶。

但总有点牵强的成分。

那还这么理解呢:我就认为爬到第0层,方法就是0啊,一步只能走一个台阶或者两个台阶,然而楼层是0,直接站楼顶上了,就是不用方法,dp[0]就应该是0.

其实这么争论下去没有意义,大部分解释说dp[0]应该为1的理由其实是因为dp[0]=1的话在递推的过程中,i2开始遍历本题就能过,然后就往结果上靠去解释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数组

举例当n5的时候,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.斐波那契数题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。

而本题,就需要逐个分析了,大家现在应该初步感受出动规五部曲的重要性了。

简单题是用来掌握方法论的,例如昨天斐波那契的题目够简单了吧,但昨天和今天可以使用一套方法分析出来的,这就是方法论!

所以不要轻视简单题,那种凭感觉就刷过去了,其实和没掌握区别不大,只有掌握方法论并说清一二三,才能触类旁通,举一反三哈!

相关推荐
江奖蒋犟1 小时前
【初阶数据结构】排序——归并排序
c语言·数据结构·算法
cdut_suye1 小时前
STL之list篇(下)(从底层分析实现list容器,逐步剥开list的外表)
开发语言·数据结构·c++·学习·算法·stl·list
大地之灯1 小时前
深度学习每周学习总结J1(ResNet-50算法实战与解析 - 鸟类识别)
人工智能·python·深度学习·学习·算法
流星白龙2 小时前
【C++算法】9.双指针_四数之和
开发语言·c++·算法
闻缺陷则喜何志丹2 小时前
【C++差分数组】2381. 字母移位 II|1793
c++·算法·字符串·力扣·差分数组·移位·方向
C++忠实粉丝2 小时前
模拟算法(1)_替换所有的问号
人工智能·算法
源代码•宸2 小时前
Leetcode—76. 最小覆盖子串【困难】
c++·经验分享·算法·leetcode·滑动窗口
van叶~2 小时前
杂谈c语言——6.浮点数的存储
c语言·算法
高一学习c++会秃头吗2 小时前
c++结构体传参
开发语言·c++·算法
Felix Du2 小时前
数据结构与算法(七)静态链表
c语言·数据结构·学习·算法·链表