本文章以有限制的楼梯攀登一题的思路讲解动态规划的基础思路
前置题目讲解
在刚开始接触到算法时,接触到一些基础算法题的你肯定遇到一种题:爬楼梯,题目大概如下:
- 假设你正在爬楼梯。需要
n阶你才能到达楼顶。 - 每次你可以爬
1或2个台阶。你有多少种不同的方法可以爬到楼顶呢?
在刚开始思考时,会感到无从下手,高中数学的排列组合在脑子里回荡,但始终无法得到答案,这是因为,我们习惯于从整体,正向方向去思考问题。
假设给出的阶梯很长,我们不妨设想从最后几层阶梯开始思考,因为那样思考起来更为简单,爬楼梯的所有可能方式的数量几乎可以手算,例如:
- 从倒数第一个阶梯开始,只有一种方法:爬
1个台阶 - 从倒数第二个阶梯开始,有两种方式:
1 1、2 - 从倒数第三个阶梯开始,有三种方式:
1 1 1、1 2、2 1 - 从倒数第四个阶梯开始,有五种方式:
1 1 2、2 2、1 1 1 1、1 2 1、2 1 1 - ······
到这里,我们应该能发现规律:这不就是常见的斐波那契数列?!
所以我们可以得到思路:只要将首项和第二项初始化为1和2,再使用斐波那契数列的规律从后往前计算,我们便可以得出结果,即:
这便是较为简单的动态规划,我们在计算某一步时,不需要一次次的从后往前计算,而是使用数组将已经计算过的结果保存起来,需要时直接使用,而不需要重新计算。
该数组通常命名为 dp ,即:
使用 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)是一个由一系列数字组成的数列,其中每个数字都是前两个数字之和。它的定义如下:
回看爬楼梯这道题,我们可以手算出倒数第一和第二的方式分别为1和2,从倒数第三个阶梯开始,实际上可以看作:倒数第一个台阶再爬 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阶且最后一步是走两阶的方法数
列出递推式如下:
最后返回数组的和,即所有方法数即可
使用 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阶的方法数
文章到此结束,希望能对你的算法刷题之旅有所启发