大家好,今天带大家做一道动态规划入门级别的问题,三步问题。
一、题目解析
好,我们来看这道题,有一个小孩,它在上楼梯,这个楼梯有n阶台阶,小孩可以一次上1阶,也可以一次上2阶,也可以一次上3阶。现在让我们计算一下这个小孩子有几种上楼梯的方式。这个结果很大,因此我们在计算结果的时候需要取模。题目还是很简单的,那么我们来分析一下这个问题。
二、算法原理
我没有学动态规划的时候,第一次看到这道题的时候,其实很懵逼的,根本无从下手呀,第一感觉就是好抽象,有好多可能性,我怎么知道呢?不知道大家是不是这种想法。好,接下来带大家分析一下这道题。
第一个台阶:
我用0表示地平线,1、2、3、4就表示有几号台阶。好我们先来看,我们走到第一个台阶有几种方式?就一种呀,我直接从地平线走一步上到到第一个台阶上。一共有一种方式。
第二个台阶:
可以直接从地平线走一步上两个台阶到达第二个台阶。一共有一种方式。
我也可以从第一个台阶再走一步走一个台阶走到第二个台阶,就是从第一步的后面再加上一步就可以到达第二个台阶。一共有两种方式。
第三个台阶:
可以直接从地平线走一步上三个台阶到达第三个台阶,此时方法数+1。
也可以从第一个台阶在走一步上两个台阶到达第三个台阶。其实就是我从第一步的方法后面在加一步就可以,走第一个台阶有一种方式,因此方法数在+1即可。
也可以从第二个台阶走一步上一个台阶走到第三个台阶。就是在走到第二步的每一种方法后面再加上一步,在走上一步就可以到达第三个台阶。走到第二个台阶有两种方法,因此此时方法数在+2即可。
因此上第三个台阶一共有四种方式。
第四个台阶:
可以从第一个台阶走一步上三个台阶到达第四个台阶,在到第一个台阶的方式上在多加一步,就可以到第四个台阶,到达第一个台阶只有一种方法,因此方法数+1
也有从第二个台阶上走一步上二个台阶到达第四个台阶,走到第二个台阶上有二种方式,在这两种方式的后面分别在走上一步,就ok了,此时方法数在+2。
也可以从第三个台阶走上一步上一个台阶到达第四个台阶,走到第三个台阶上有四种方式,在这四种方式的后面分别在走上一步,就ok了,此时方法数在+4。
此时上第四个台阶一共有七种方式。
从上述分析中,我们可以发现一个什么东西?我们看上第四个台阶的方法数,是不是就是上前三个台阶方法的和呢?是不是就和我们上一道泰波那契那道题的状态转移方程一样呢?
如果你不信,你可以在自己推导一下第五个台阶的方式。
状态表示:
dp[i]:楼梯有i阶台阶,小孩子上楼梯总共有几种方式。这道题的状态表示怎么来的呢?其实就是根据题目要求得来的。这里提一下,数组的下标是从0开始的,因此,0号下标其实是没有意义的,因为没有0个台阶,所以我们开空间的时候要开n + 1个,然后0号位置直接空过,填表的时候从1号下标开始填。初始化的时候也是从1号下标开始初始化。当然如果你开n个空间也可以。
状态转移方程:
根据我们上面的分析我们可以知道,从第四个台阶开始,方法数就是前面三个台阶方法数的和。因此.dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
初始化:
因为要用到前3个位置的状态,因此我们需要将dp表1、2、3下标的位置初始化,初始化成1,2,4即可。这是从上面分析得来的。
填表顺序:
这道题和泰波那契那道题一样,要从左往右填写。
返回值:
要求第n个台阶的方法数,因此返回dp[n]即可。这里开的空间是n+1。
接下来就是编写代码,这道题恶心就恶心在它的数据上。你写一下你就知道了。
面试题 08.01. 三步问题 - 力扣(LeetCode)
三、代码编写
这里要注意的地方就是你的数据是有可能超出存储范围的,因此你需要对它取模,而且有可能是两个数相加的和超出了范围,这是这道题恶心的地方。
class Solution
{
const int mod = 1e9 + 7;
public:
int waysToStep(int n)
{
if(n == 1 || n == 2) return n;
if(n == 3) return 4;
//1、创建dp表
vector<int> dp(n + 1);
//2、初始化
dp[1] = 1, dp[2] = 2, dp[3] = 4;
//3、填表
for(int i = 4; i <= n; i++)
{
dp[i] = ((dp[i - 1] + dp[i - 2]) % mod + dp[i - 3]) % mod ;
}
//4、返回值
return dp[n];
}
};