面试题 08.01. 三步问题https://leetcode.cn/problems/three-steps-problem-lcci/description/三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1e9 + 7。
- 输入:n = 3,输出:4,说明:有4种走法,分别是:连续3次,每次上1阶楼梯;先上1阶楼梯,再上2阶楼梯;先上2阶楼梯,再上1阶楼梯;直接上3阶楼梯。
- 输入:n = 5,输出:13。
提示:n的范围在[1, 1e6]之间。
我们用动态规划的思想来解决这个问题。
确定状态表示:根据经验和题目要求,我们用dp[i]表示到达i位置时,一共有多少种方法。
推导状态转移方程:以i位置最近的一步,来分类讨论。小孩到达i位置的方法数,应该等于上一步的所有方法之和。
- 上一步上1阶楼梯,方法数为:到达i - 1位置的方法数,即dp[i - 1]。
- 上一步上2阶楼梯,方法数为:到达i - 2位置的方法数,即dp[i - 2]。
- 上一步上3阶楼梯,方法数为:到达i - 3位置的方法数,即dp[i - 3]。
综上,得到dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]。
初始化:根据状态转移方程,当我们计算dp[0],dp[1]和dp[2]时会越界,需要初始化。
- dp[0]表示到达下标为0的台阶,一共有多少种方法,无意义。
- dp[1]表示到达下标为1的台阶,一共有多少种方法。只有1种方法,即直接上1阶楼梯,故dp[1] = 1。
- dp[2]表示到达下标为2的台阶,一共有多少种方法。有2种方法,分别是:连续2次,每次上1阶楼梯;直接上2阶楼梯。故dp[2] = 2。
- 由于dp[0]无意义,所以我们考虑dp[3]。dp[3]表示到达下标为2的台阶,一共有多少种方法。有4种方法,分别是:连续3次,每次上1阶楼梯;先上1阶楼梯,再上2阶楼梯;先上2阶楼梯,再上1阶楼梯;直接上3阶楼梯。故dp[3] = 4。
- 由于dp[3] = dp[0] + dp[1] + dp[2],所以dp[0] = dp[3] - dp[1] - dp[2] = 4 - 1 - 2 = 1。
综上所述,dp[0] = dp[1] = 1,dp[2] = 2。
填表顺序:根据状态转移方程,dp[i]依赖于dp[i - 1],dp[i - 2]和dp[i - 3],故应该从左往右填表。
返回值:根据状态表示,应返回dp[n],表示到达n位置时,一共有多少种方法。
细节问题:由于下标的范围是[0, n],故dp表的规模是1 x (n + 1)。为了防止dp[0],dp[1]和dp[2]越界,需要处理边界情况。由于结果可能很大,为了防止溢出,在每次加法计算之后,都需要对结果模1e9 + 7。
class Solution {
public:
int waysToStep(int n) {
const int MOD = 1000000007;
// 处理边界情况
if (n == 0 || n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
// 创建dp表
vector<int> dp(n + 1);
// 初始化
dp[0] = dp[1] = 1;
dp[2] = 2;
// 填表
for (int i = 3; i <= n; i++) {
dp[i] = ((dp[i - 1] + dp[i - 2]) % MOD + dp[i - 3]) % MOD;
}
// 返回结果
return dp[n];
}
};