来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/climbing-stairs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
看到题目感觉像是动态规划的问题,但是for循环的暴力破解应该也可以。写的时候感觉像递归?
(1)动态规划
链接:https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/
我们用 f(x)f(x) 表示爬到第 xx 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子:
f(x) = f(x - 1) + f(x - 2)
它意味着爬到第 x级台阶的方案数是爬到第 x - 1级台阶的方案数和爬到第 x - 2级台阶的方案数的和。很好理解,因为每次只能爬 1 级或 2 级,所以 f(x) 只能从 f(x−1) 和 f(x - 2)转移过来,而这里要统计方案总数,我们就需要对这两项的贡献求和。
以上是动态规划的转移方程,下面我们来讨论边界条件。我们是从第 0 级开始爬的,所以从第 0 级爬到第 0 级我们可以看作只有一种方案,即 f(0) =1 ;从第 0 级到第 1 级也只有一种方案,即爬一级,f(1) = 1。这两个作为边界条件就可以继续向后推导出第 n级的正确结果。我们不妨写几项来验证一下,根据转移方程得到 f(2) = 2,f(3) = 3,f(4) = 5,……,我们把这些情况都枚举出来,发现计算的结果是正确的。
我们不难通过转移方程和边界条件给出一个时间复杂度和空间复杂度都是 O(n) 的实现,但是由于这里的 f(x) 只和 f(x - 1)与 f(x - 2)有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1)。下面的代码中给出的就是这种实现
class Solution:
def climbStairs(self, n: int) -> int:
p, q, r = 0, 0, 1
res = n # 执行n次
while res > 0:
p = q # f(x-2)
q = r # f(x-1)
r = p + q #f(x)
res -= 1
return r
复杂度分析
时间复杂度:循环执行 nn 次,每次花费常数的时间代价,故渐进时间复杂度为 O(n)O(n)。
空间复杂度:这里只用了常数个变量作为辅助空间,故渐进空间复杂度为 O(1)O(1)。
作者:Gnakuw https://leetcode-cn.com/u/nehzil/
思路:
一般看到题目有多少种方案,这种题意思不需要你列出来具体的多少种方案只需要计算有多少种方案,基本上就可以尝试用动态规划算法来解决问题。想好用动态规划就想想动态规划五步曲。
关键词:有多少种方案 算法
- 确定dp数组以及下标的含义 定义dp[i]为爬上第 i 级台阶有多少种方案;
- 确定状态转移方程因为每次只可以爬1或者2个台阶所以,爬上当前台阶的方案应该是前面两个状态的方案的和即,dp[i] = dp[i-1] + dp[i-2]。
- 初始化状态 i = 0 级开始爬的,所以从第 0 级爬到第 0 级我们可以看作只有一种方案,即 dp(0)=1; i = 1 代表从第 0 级到第 1 级也只有一种方案,即爬一级,dp(1) = 1。
- 遍历顺序 由状态转移方程知道dp[i] 是从 dp[i−1] 和dp[i−2] 转移过来所以从前往后遍历。
- 返回值 因为一共计算 n 阶楼梯有多少方案,所以返回dp[n]。
滚动数组的思想
https://blog.youkuaiyun.com/m0_46427179/article/details/107419492
滚动数组是一种能够在动态规划中降低空间复杂度的方法,简要来说就是通过观察dp方程来判断需要使用哪些数据,可以抛弃哪些数据,一旦找到关系(递推方程/状态转移方程),就可以用新的数据不断覆盖旧的数据量来减少空间的使用。
(2)矩阵快速幂
https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/
如何想到使用矩阵快速幂?
(3)通项公式
https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/
class Solution:
def climbStairs(self, n: int) -> int:
sqrt5 = pow(5, 0.5) # 根号5
A = pow(((1 + sqrt5) / 2), n+1)
B = pow(((1 - sqrt5) / 2), n+1)#公式好像不对,公式是n
return int((A - B) / sqrt5) # 计算出来不是整数
(4,5,6的原文地址)
链接:https://leetcode-cn.com/problems/climbing-stairs/solution/zhi-xin-hua-shi-pa-lou-ti-zhi-cong-bao-l-lo1t/
(4)暴力DFS
根据递推公式f(x) = f(x - 1) + f(x - 2) 和边界值,进行递归
class Solution:
# 暴力深搜
def climbStairs(self, n: int) -> int:
if n == 0 or n == 1:#考虑边界
return 1
return self.climbStairs(n - 1) + self.climbStairs(n - 2)#还是用递推公式
(5)记忆化递归
将已经算好的值给存起来,等再次需要用到的时候,就直接取而不用计算,这样就大大节省了计算时间
# 记忆化递归,自顶向下
def climbStairs(self, n: int) -> int:
def dfs(i: int, memo) -> int:
if i == 0 or i == 1:
return 1
if memo[i] == -1:
memo[i] = dfs(i - 1, memo) + dfs(i - 2, memo)
return memo[i]
# memo: [-1] * (n - 1)
# -1 表示没有计算过,最大索引为 n,因此数组大小需要 n + 1
return dfs(n, [-1] * (n + 1)) #开辟了一个数组,存储算过的值
(6)法一的另一种解法
class Solution:
# f(n)只依赖于f(n-1)和f(n-2),只需要两项就足够了
def climbStairs(self, n: int) -> int:
a = b = 1
for i in range(2, n + 1):
a, b = b, a + b
return b