文章目录
题目链接:面试题 08.01. 三步问题
题目解析
动态规划 (Dynamic programming,简称 DP)是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法,在查找 有很多重叠子问题 的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费不必要的时间。
通常的解决步骤有五步:
- 确定状态表示/dp表的含义
- 确定状态转移方程/递推公式
- 初始化
- 确定填表顺序
- 确定返回值
题目情景是正在上楼梯,楼梯有 n 阶台阶,一次可以上 1 阶、2 阶或 3 阶。
我们需要计算 到达楼顶 有多少种上楼梯的方式。
结果可能很大,需要对结果模 1000000007。
例 1:n = 3
如图所示,一共有 4 种方式

- 方式1:先走一个台阶到1,再走一个台阶到2,最后走一个台阶到3
- 方式2:先走一个台阶到1,再走两个台阶到3
- 方式3:先走两个台阶到2,再走一个台阶到3
- 方式4:走三个台阶到3
返回 4。
算法原理
确定状态表示
状态表示/dp表含义可以从题目要求找出,也可以从重复子问题中找出。
根据题目,我们可以直接确定:dpi 表示到达第 i 个台阶的方式。
确定状态转移方程
题目给出一个条件:一次可以上 1 阶、2 阶或 3 阶,这直接就可以让我们得出状态转移方程。
现在假设要求到达第四个台阶有几种方式,怎么求?无非三种情况:
- 先到达第三个台阶,然后走一个台阶到达,所求的方式就等于到达第三个台阶的方式
- 先到达第二个台阶,然后走两个台阶到达,所求的方式就等于到达第二个台阶的方式
- 先到达第一个台阶,然后走三个台阶到达,所求的方式就等于到达第一个台阶的方式
因此到达第四个台阶的方式就是这三种情况之和,即 到达第三个台阶的方式 + 到达第二个台阶的方式 + 到达第一个台阶的方式 ,写成式子就是 dp[4] = dp[3] + dp[2] + dp[1]。
所以状态转移方程:dp[i] = dp[i-1] + dp[i-2] + dp[i-3]。
初始化
根据状态转移方程 dp[i] = dp[i-1] + dp[i-2] + dp[i-3],我们可以计算所有 i > 3 的 dp 值,但是当 i 为 1、i 为 2 时,就会越界访问。
由于 i = 0 时表示位于原地,其方式为 0,不需要讨论。而当 i = 1 时,只有一种方式那就是走一步到达第一个台阶,即 dp[1] 初始值为 1;i = 2 时可以先走一步到第一个台阶,再走一步到第二个台阶,也可以直接走两个台阶,因此 dp[2] 初始值为 2。
我们根据例1可以知道 i = 3 时,dp[3] = 4,这就是一个初始化条件。
确定填表顺序
假如我们此时只知道 n 为 1、2、3 时对应的到达方式,需要求第 5 个台阶的到达方式,能直接代入状态转移方程 dp[i] = dp[i-1] + dp[i-2] + dp[i-3] 求解吗?
答案是不行的,因为状态转移方程告诉我们,必须要在前三个都已知的情况下才可以代入求解,而在这个例子里,我们还没有求解第 4 个台阶的到达方式,因此无法直接计算第 5 个台阶的到达方式,必须先计算前一个才行,这就是 dp 表的填表顺序,本题是 从左往右 的顺序。
确定返回值
本题的返回值题干已经告诉我们了:有多少种上楼梯的方式 ,也就是 dp[n]。
细节问题
- 考虑边界情况:n 为 1 或者 2 时怎么处理?n 为 3 时怎么处理?
- 本题需要访问下标为
n的数,因此开辟 dp 数组的时候大小应是n+1。 - 本题需要在计算 dpi 的时候,每进行一次加法运算就模一次 1000000007,否则结果会溢出
代码实现
java
class Solution {
public int waysToStep(int n) {
// 边界情况
if (n < 3) {
return n;
}
if (n == 3) {
return 4;
}
// dp表
int[] dp = new int[n+1];
// 初始化
dp[1] = 1;
dp[2] = 2;
dp[3] = 4;
// 填表
int MOD = (int) 1e9 + 7;
for (int i = 4; i <= n; i++) {
dp[i] = ((dp[i-1] + dp[i-2])%MOD + dp[i-3])%MOD;
}
// 返回结果
return dp[n];
}
}
完