70. 爬楼梯

70. 爬楼梯

70. 爬楼梯

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

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

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1+ 12. 2

示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1+ 1+ 12. 1+ 23. 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代码如下:

版本一

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)

当然依然也可以,优化一下空间复杂度,代码如下:

版本二

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

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

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

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

<think>我们被要求回答LeetCode70题“爬楼梯”的解题思路,重点在动态规划和递归方法。根据引用内容,我们可以总结如下:1.问题描述:爬楼梯,每次可以1或2个台阶,到达第n阶有多少种不同的方法。2.动态规划是解决此问题的典型方法,状态转移方程为:dp[i]=dp[i-1]+dp[i-2]。3.递归方法虽然直观,但存在重复计算,可能导致超时,可以使用记忆化搜索优化(Top-DownDP)。4.动态规划(自底向上)通常使用数组存储中间结果,然后进行空间优化(例如用两个变量代替数组)。我们将按照以下结构组织回答:一、问题描述二、思路分析(递归关系)三、递归方法(及优化)四、动态规划方法(及优化)五、总结同时,根据要求,我们需要在回答末尾生成相关问题。注意:所有数学表达式(包括变量、方程)使用$...$格式,独立公式使用$$...$$格式。回答使用中文。引用标识:在引用的内容末尾添加[^1]、[^2]等,但注意我们引用的内容来自用户提供的引用[1]到[4]。</think>### LeetCode 70爬楼梯|动态规划与递归解析 #### 一、问题描述 给定楼梯阶数 $n$,每次可 $1$ 或 $2$ 阶,求到达第 $n$ 阶的不同方法总数。 #### 二、核心思路 **状态转移方程**: $$ dp[i] = dp[i-1] + dp[i-2] $$ 其中 $dp[i]$ 表示到达第 $i$ 阶的方法数。该方程源于:第 $i$ 阶只能从 $i-1$ 阶( $1$ 阶)或 $i-2$ 阶( $2$ 阶)到达[^2][^4]。 --- #### 三、动态规划解法(推荐) **自底向上迭代**,避免递归重复计算: ```python def climbStairs(n: int) -> int: if n <= 2: return n # 边界处理 dp = [0] * (n+1) dp[1], dp[2] = 1, 2 # 初始状态 for i in range(3, n+1): dp[i] = dp[i-1] + dp[i-2] # 状态转移 return dp[n] ``` **空间优化**(滚动变量): 仅需存储前两个状态,空间复杂度优化至 $O(1)$: ```python def climbStairs(n: int) -> int: if n <= 2: return n a, b = 1, 2 # a=dp[i-2], b=dp[i-1] for _ in range(3, n+1): a, b = b, a + b # 更新状态 return b ``` --- #### 四、递归解法(需优化) **基础递归**(不推荐): 直接实现状态方程,但存在指数级重复计算: ```python def climbStairs(n: int) -> int: if n == 1: return 1 if n == 2: return 2 return climbStairs(n-1) + climbStairs(n-2) # 递归分解 ``` **问题**:计算 $dp[5]$ 需重复计算 $dp[3]$ 和 $dp[4]$,时间复杂度 $O(2^n)$,$n=45$ 时超时[^3]。 **优化:记忆化搜索(Top-Down DP)** 添加缓存避免重复计算,时间复杂度降为 $O(n)$: ```python def climbStairs(n: int) -> int: memo = {} # 记忆字典 def dfs(k): if k <= 2: return k if k not in memo: memo[k] = dfs(k-1) + dfs(k-2) # 缓存结果 return memo[k] return dfs(n) ``` --- #### 五、复杂度对比 | 方法 | 时间复杂度 | 空间复杂度 | 适用性 | |---------------|------------|------------|----------------| | 基础递归 | $O(2^n)$ | $O(n)$ | 仅小 $n$ | | 记忆化搜索 | $O(n)$ | $O(n)$ | 需额外缓存 | | 动态规划 | $O(n)$ | $O(n)$ | 通用 | | **空间优化DP**| $O(n)$ | $O(1)$ | **最优解** | > **总结**:动态规划(尤其空间优化版)是本题最佳解法。其本质是斐波那契数列问题,状态转移方程 $dp[i]=dp[i-1]+dp[i-2]$ 是核心[^1][^2]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值