一、动态规划解题模版
- 状态表示:一般创建一个一维数组 dp,把 dp 表填满,其中的某一个值就是结果。状态表示指 dp 表中元素的含义。
- 来源:题目要求、经验 + 题目要求,分析问题的过程中的重复子问题
- 状态转移方程:dp[i] = ?
- 初始化:保证根据状态转移方程填表时不越界
- 填表顺序:为了填写当前状态的时候,所需要的状态已经计算过了
- 返回值:题目要求 + 状态表示
二、第 N 个泰波那契数
题目描述:第四个数是前三个数的和,第一二三个数定为 0 1 1,返回第 n 个数。

题目解析:
解题思路:
- 状态表示:dp[i] 表示第 i 个泰波那契数
- 状态转移方程:dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
- 初始化:dp[0] = 0, dp[1] = 1, dp[2] = 1
- 填表顺序:顺序从左向右填表即可
- 返回值:dp[n]
解题代码:
// 时间复杂度:O(N) // 空间复杂度:O(N)
class Solution {
public int tribonacci(int n) {
if (n < 3) return n == 0 ? 0 : 1;
// 创建 dp 表
int[] dp = new int[n + 1];
// 初始化
dp[0] = 0; dp[1] = dp[2] = 1;
// 填表
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
}
// 返回值
return dp[n];
}
}
空间优化:只创建长度为三的数组即可:
// 时间复杂度:O(N) // 空间复杂度:O(1)
class Solution {
public int tribonacci(int n) {
if (n < 3) return n == 0 ? 0 : 1;
// 创建 dp 表
int[] dp = new int[]{0, 1, 1};
// 填表
for (int i = 3; i <= n; i++) {
int tmp = dp[0];
dp[0] = dp[1];
dp[1] = dp[2];
dp[2] = tmp + dp[0] + dp[1];
}
// 返回值
return dp[2];
}
}
三、面试题 08.01. 三步问题
题目描述:

题目解析:
- 每一次上楼梯有三个选择:1, 2, 3,返回走到第 n 阶梯有多少种走法
解题思路:
- 状态表示:dp[i] 表示第 i 阶的上楼方式种类数
- 状态转移方程:dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
- 初始化:dp[0] = 1, dp[1] = 1, dp[2] = 2(小孩有 3 种走法,那么小孩的上一个台阶的可能就是 i-1, i-2, i-3)
- 填表顺序:顺序从左向右填表即可
- 返回值:dp[n]
解题代码:
// 时间复杂度:O(N) // 空间复杂度:O(N)
class Solution {
public int waysToStep(int n) {
if (n < 3) return n;
// 建立 dp 表
int[] dp = new int[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]) % 1000000007 + dp[i - 3]) % 1000000007;
}
// 返回值
return dp[n];
}
}
四、746. 使用最小花费爬楼梯(easy)
题目描述:
题目解析:
- 给我们一个 cost 数组,表示走出这一个台阶需要的花费
- 可以选择第一步从 cost[0] 还是 cost[1] 开始
- 让我们返回到达楼顶的最小总花费
解题思路:
- 状态表示:dp[i] 表示走到第 i 阶的最小花费
- 状态转移方程:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
- 初始化:表的长度要比 cost 大 1,表示楼顶
- 填表顺序:顺序从左向右填表即可
- 返回值:dp[n]
解题代码:
// 时间复杂度:O(N) // 空间复杂度:O(N)
class Solution {
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
if (n == 2) return Math.min(cost[0], cost[1]);
// 状态转移表
int[] dp = new int[n + 1];
// 状态转移方程
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[n];
}
}
五、91.解码方法
题目描述:

题目解析:
- 给我们一个编码规则,数字 1 - 26 对应表示字母 A - Z
- 给我们一个纯数字的字符串,让我们将字符串进行拆分,拆分后的字符串,对应的所有子串都可以对应表示成字母
- 求可以拆分的种数
解题思路:
- 状态表示:dp[i] 表示字符串中 0 到 i 字符的合法拆分结果
- 状态转移方程:
- i 位置上字符为 1 到 9 的字符单独解码成功 dp[i] += dp[i - 1]
- i 与 i-1 位置上的字符组合是 10 到 26 的合法编码 dp[i] += dp[i - 2]
- 初始化:
- dp[0]:第一个字符不为 0,就 dp[0] = 1,为 0 就返回 0
- dp[1]:该字符不为 0,dp[1] += dp[0],与第 0 个字符组成合法编码 10 - 26,dp[1] += 1
填表顺序:从左到右 返回值:dp[n - 1]
解题代码:
class Solution {
public int numDecodings(String s) {
// 前导 0
if (s.charAt(0) == '0') return 0;
int n = s.length();
char[] ch = s.toCharArray();
// 只有一个字符
if (n == 1) return 1;
int[] dp = new int[n];
// 初始化
dp[0] = 1;
// 初始化第二个元素
if (ch[1] != '0' && ch[0] != '0') dp[1] += 1;
int t = (ch[1] - '0') + (ch[0] - '0') * 10;
if (t >= 10 && t <= 26) dp[1] += 1;
// 填表
for (int i = 2; i < n; i++) {
// 该字符单独组
if (ch[i] != '0') dp[i] += dp[i - 1];
// 与前一个字符组
t = (ch[i] - '0') + (ch[i - 1] - '0') * 10;
if (t >= 10 && t <= 26) dp[i] += dp[i - 2];
}
return dp[n - 1];
}
}


