简单的动态规划 | 豆包MarsCode AI刷题

152 阅读5分钟

本文章以有限制的楼梯攀登一题的思路讲解动态规划的基础思路

前置题目讲解

在刚开始接触到算法时,接触到一些基础算法题的你肯定遇到一种题:爬楼梯,题目大概如下:

  • 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
  • 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

在刚开始思考时,会感到无从下手,高中数学的排列组合在脑子里回荡,但始终无法得到答案,这是因为,我们习惯于从整体,正向方向去思考问题。

假设给出的阶梯很长,我们不妨设想从最后几层阶梯开始思考,因为那样思考起来更为简单,爬楼梯的所有可能方式的数量几乎可以手算,例如:

  • 从倒数第一个阶梯开始,只有一种方法:爬 1个台阶
  • 从倒数第二个阶梯开始,有两种方式:1 12
  • 从倒数第三个阶梯开始,有三种方式:1 1 11 22 1
  • 从倒数第四个阶梯开始,有五种方式:1 1 22 21 1 1 11 2 12 1 1
  • ······

到这里,我们应该能发现规律:这不就是常见的斐波那契数列?!

所以我们可以得到思路:只要将首项和第二项初始化为12,再使用斐波那契数列的规律从后往前计算,我们便可以得出结果,即:

step[n]=step[n1]+step[n2]step[n]=step[n−1]+step[n−2]

这便是较为简单的动态规划,我们在计算某一步时,不需要一次次的从后往前计算,而是使用数组将已经计算过的结果保存起来,需要时直接使用,而不需要重新计算。

该数组通常命名为 dp ,即:

dp[n]=dp[n1]+dp[n2]dp[n]=dp[n−1]+dp[n−2]

使用 Python 编写代码如下:

def climbStairs(n):
    if n <= 2:
        return n

    dp = [0] * (n + 1)
    dp[1] = 1
    dp[2] = 2

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

    return dp[n]

此时,我们还需要搞清楚一个问题,为什么这个题目会符合斐波那契数列的规律?

斐波那契数列(Fibonacci Sequence)是一个由一系列数字组成的数列,其中每个数字都是前两个数字之和。它的定义如下:

F(0)=0,F(1)=1F(0)=0F(0)=0,F(1)=1F(0) = 0
F(n)=F(n1)+F(n2)(for n2)F(n)=F(n−1)+F(n−2)(for n≥2)

回看爬楼梯这道题,我们可以手算出倒数第一和第二的方式分别为12,从倒数第三个阶梯开始,实际上可以看作:倒数第一个台阶再爬 2 个台阶和倒数第二个台阶再爬 1 个台阶

即倒数第一个台阶的所有可能后面再加个 2,即倒数第二个台阶的所有可能后面再加个 1

也即倒数第一个台阶的所有可能 + 倒数第二个台阶的所有可能

将这个规律推广至一般:到达第 n 阶的方法数 = 到达第 n−1 阶的方法数 + 到达第 n−2 阶的方法数

而这个规律刚好符合斐波那契数列,所以可以使用其进行计算

进阶题目讲解

如果再给题目加点难度:

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

在理解之前的题目解题思路之后,我们便能很轻易的得出该题的思路:

既然不能连续走两步,那把之前计算出来的方法数减去出现过 ··· 2 2 ··· 的方法就可以得出答案

但要实现这个思路,我们就必须得出:那些方法出现过 ··· 2 2 ···,在上题的解法中,我们并没有记录这一信息,因此无法直接得出答案。我们需要使用某种方式记录该信息,以便我们减去不需要的方法

要记录信息,我们便要知道需要减去的那部分会出现在哪:仅在到达 n-2 阶的最后一步走了 2 阶的时候,我们需要将其减去

我们便可以使用一个数组来存储:

  • dp[n][0] 表示到达第n阶且最后一步是走一阶的方法数
  • dp[n][1] 表示到达第n阶且最后一步是走两阶的方法数

列出递推式如下:

dp[i][0]=dp[i1][0]+dp[i1][1]dp[i][0] = dp[i-1][0] + dp[i-1][1]
dp[i][1]=dp[i2][0]dp[i][1] = dp[i-2][0]

最后返回数组的和,即所有方法数即可

使用 Python 编写代码如下;

def climbStairsWithLimit(n):
    if n == 0:
        return 1
    if n == 1 or n == 2:
        return n

    # dp[n][0] 表示到达第n阶且最后一步是走一阶的方法数
    # dp[n][1] 表示到达第n阶且最后一步是走两阶的方法数
    dp = [[0, 0] for _ in range(n+1)]

    dp[1][0] = 1
    dp[2][0] = 1
    dp[2][1] = 1

    for i in range(3, n+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阶的方法数

文章到此结束,希望能对你的算法刷题之旅有所启发