青训营X豆包MarsCode 技术训练营 AI刷题124. 有限制的楼梯攀登 | 豆包MarsCode AI 刷题

63 阅读5分钟

问题描述

小U最近决定挑战一座非常高的楼梯,每次他可以选择走一步或两步,但有一个重要的限制:他不能连续走两步。因此,小U想知道他总共有多少种不同的方式可以从楼梯的底部走到顶端。

你需要帮他计算在给定的楼梯层数下,小U有多少种走法。

测试样例

样例1:

输入:n = 2
输出:2

样例2:

输入:n = 3
输出:3

样例3:

输入:n = 4
输出:4

思路

首先我们分析一个简化的情况:

1. 走1步的情况:

  • 若楼梯的层数为 1 (n=1),小U只有一种选择,即走一步。
  • 若楼梯的层数为 2 (n=2),小U有两种选择:走两步(2个1步),或者一次性走两步。

2. 走两步的情况:

  • 若楼梯的层数大于2时,我们可以递推地分析问题。对于一个楼梯 n 层,我们可以分为两种情况:

    • 如果小U的最后一步是走一步,那么他就必须从 n-1 层的楼梯爬到顶。
    • 如果小U的最后一步是走两步,那么他就必须从 n-2 层的楼梯爬到顶。

因此,假设 dp[i] 表示从第0层爬到第i层的不同走法数,则有递推关系:

[ dp[i] = dp[i-1] + dp[i-2] ]

其中:

  • dp[i-1] 代表最后一步是一步,从第 i-1 层到第 i 层。
  • dp[i-2] 代表最后一步是两步,从第 i-2 层到第 i 层。

初始条件:

  • dp[0] = 1(站在地面上)
  • dp[1] = 1(爬到第一层只有一种方式,就是走一步)

动态规划实现

我们通过动态规划计算 dp 数组来得到最终结果。递推式的复杂度是 O(n),因此整个算法的时间复杂度是 O(n)。

代码

def solution(n):
    # Edit your code here
    if n == 1:
        return 1
    if n == 2:
        return 2  # 到达第二层可以选择一步或两步
    dp = [[0] * 2 for _ in range(n + 1)]
    dp[0][0], dp[0][1] = 1, 0
    dp[1][0], dp[1][1] = 1, 0
    dp[2][0], dp[2][1] = 1, 1

    for i in range(3, n + 1):
        #0表示上一步走的是一步,1表示上一步走的是两步
        dp[i][0] = dp[i - 1][0] + dp[i - 1][1]  # 最后一步走了一步
        dp[i][1] = dp[i - 2][0]                # 最后一步走了两步

    return dp[n][0] + dp[n][1]  # 返回到达第 n 层的所有方式


if __name__ == "__main__":
    # Add your test cases here

    print(solution(2) == 2)

1. 基础情况处理

if n == 1:
    return 1
if n == 2:
    return 2  # 到达第二层可以选择一步或两步
  • 当 n == 1 时,表示楼梯只有一层,只有一种走法,就是走一步。
  • 当 n == 2 时,楼梯有两层,可以通过走一步两次,或者直接走两步,所以有两种走法。

这些基础情况对于递推过程非常重要,它们提供了边界条件。

2. 状态定义和初始化

dp = [[0] * 2 for _ in range(n + 1)]
dp[0][0], dp[0][1] = 1, 0
dp[1][0], dp[1][1] = 1, 0
dp[2][0], dp[2][1] = 1, 1
  • 这里定义了一个二维数组 dp,其中 dp[i][0] 表示到达第 i 层时,最后一步是走一步的走法数;dp[i][1] 表示到达第 i 层时,最后一步是走两步的走法数。
  • dp[0][0] = 1, dp[0][1] = 0:表示从地面到第0层(即已经在地面上)时,最后一步没有走,走法为 1(显然只有一种方式站在原地)。
  • dp[1][0] = 1, dp[1][1] = 0:表示从第0层到第1层时,只有一步走法,所以 dp[1][0] = 1dp[1][1] = 0
  • dp[2][0] = 1, dp[2][1] = 1:表示到达第2层时,可以通过走一步从第1层到达,或者走两步直接从第0层到达。因此 dp[2][0] = 1dp[2][1] = 1

3. 状态转移

for i in range(3, n + 1):
    # 0表示上一步走的是一步,1表示上一步走的是两步
    dp[i][0] = dp[i - 1][0] + dp[i - 1][1]  # 最后一步走了一步
    dp[i][1] = dp[i - 2][0]                # 最后一步走了两步
  • dp[i][0] = dp[i - 1][0] + dp[i - 1][1] :表示到达第 i 层的最后一步走的是一步,可以从第 i-1 层通过走一步到达。dp[i-1][0] 表示从第 i-1 层且上一步走的是一步,dp[i-1][1] 表示从第 i-1 层且上一步走的是两步。总的来说,只要上一步走了 1 步或者 2 步,都可以从第 i-1 层到达第 i 层。
  • dp[i][1] = dp[i - 2][0] :表示到达第 i 层的最后一步走了两步,只能从第 i-2 层通过走两步到达。此时,上一步走了一步的情况 (dp[i-2][0]) 是唯一的选择。

4. 返回结果

return dp[n][0] + dp[n][1]  # 返回到达第 n 层的所有方式
  • 最后返回到达第 n 层的所有可能性,实际上是 dp[n][0] + dp[n][1],因为最后一步可以是走一步或者走两步,二者的走法数之和即为总的走法数。

5. 时间复杂度分析

  • 初始化二维数组 dp 的时间复杂度是 O(n),因为数组的大小是 (n+1) * 2
  • 填充 dp 数组的时间复杂度是 O(n),因为你只需要进行一次循环,循环中每次操作都是常数时间的。
  • 因此,整个算法的时间复杂度是 O(n)。

6. 空间复杂度分析

  • 使用了一个 dp 数组,大小为 (n+1) * 2,因此空间复杂度是 O(n)。

image.png