题目

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

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

注意:给定 n 是一个正整数。

示例 1:

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

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/climbing-stairs

思路

对于这样一个问题,我们先来思考下能否递归的解决,它内部是否有递归的结构。

我们自顶向下的思考问题,直接考虑第LeetCode刷题——70. 爬楼梯_斐波那契数列阶台阶,并且假设它下面的LeetCode刷题——70. 爬楼梯_斐波那契数列_02阶,LeetCode刷题——70. 爬楼梯_leetcode_03阶等子问题都已经解决了。

如果我们要想爬上LeetCode刷题——70. 爬楼梯_斐波那契数列阶台阶,因为一次要么爬LeetCode刷题——70. 爬楼梯_leetcode_05阶,要么爬LeetCode刷题——70. 爬楼梯_leetcode_06阶,所以爬LeetCode刷题——70. 爬楼梯_斐波那契数列阶台阶有两种可能。

LeetCode刷题——70. 爬楼梯_斐波那契数列_02阶再爬LeetCode刷题——70. 爬楼梯_leetcode_05阶或者从LeetCode刷题——70. 爬楼梯_leetcode_03阶再爬LeetCode刷题——70. 爬楼梯_leetcode_06阶。

LeetCode刷题——70. 爬楼梯_leetcode_12

不难看出这是一个递归问题,我们把问题转换为爬LeetCode刷题——70. 爬楼梯_斐波那契数列_02阶有多少种方法和爬LeetCode刷题——70. 爬楼梯_leetcode_03阶有多少种方法。然后把这两个问题的答案相加就好了。这样把一个大的问题转换为两个小问题。

LeetCode刷题——70. 爬楼梯_70.爬楼梯_15


用同样的思路可以求出爬LeetCode刷题——70. 爬楼梯_斐波那契数列_02阶和爬LeetCode刷题——70. 爬楼梯_leetcode_03阶的方法数。从上面这个递归树可以看出,存在很多重复子问题。

下面我们先写出按照这种思路解决问题的递归算法。

代码

递归

class Solution:
def climbStairs(self, n: int) -> int:
# 递归的终止条件
if n == 1:
return 1
if n == 2:
return 2
return self.climbStairs(n-1) + self.climbStairs(n-2)

LeetCode刷题——70. 爬楼梯_动态规划_18


虽然思路是对的,但是递归的方式是比较低效的,这里导致了计算超时。参阅LeetCode刷题之动态规划思想,我们可以将其改成记忆化搜索的方式,解决重叠子问题。

因为爬2阶台阶有2种方法,和斐波那契数列很像。这里的递归终止条件还可以写成

if n == 0: return 1if n == 1: return 1

这样这个问题就是斐波那契数列的应用。

记忆化搜索

dp = {}

class Solution:
def climbStairs(self, n: int) -> int:
# 递归的终止条件
if n == 0:
return 1
if n == 1:
return 1
if n not in dp: # 如果没有计算过再去计算
dp[n] = self.climbStairs(n-1) + self.climbStairs(n-2)
return dp[n]

LeetCode刷题——70. 爬楼梯_斐波那契数列_19


最后改成动态规划也就很简单了。

动态规划

class Solution:
def climbStairs(self, n: int) -> int:
if n == 1:
return 1
dp = [1] * (n+1) #dp[0] = 1 , dp[1] = 1 ,dp[2]以后的通过下面的式子计算

for i in range(2,n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]

动态规划不需要递归求解,是一种自底向上的求解思想。

LeetCode刷题——70. 爬楼梯_递归_20