视频地址:https://www.bilibili.com/video/BV1xb411e7ww?from=search&seid=10475076981852959575
1. 动态规划题目类型
1.1. 按照题目求解目标分类
- 求最大最小值
- 计数:有多少种方式(走到右下角?选出k个数和为sum??)
- 求存在性(谁获胜?能不能选出k个数和为sum?)
1.2. 按照题目数据模型分类
- 坐标型
- 序列型
- 划分型
- 区间型
- 背包型
- 最长序列型博弈型
- 综合型
2. dp组成部分一:确定状态
- 状态是dp的定海神针,用一个数组记录,需要知道每个数组元素dp[i]代表什么,也可能是多维数组。
2.1. 如何确定状态?(个人认为是千变万化的dp题目中最大难点)
- 最后一步:如果尝试通过递推的方式得到最后一步?
- 子问题
- 每个子问题对应一个状态,该状态满足最优子结构
3. dp组成部分二:转移方程(难点二)
2,5,7三种硬币,最少多少种硬币拼出X元为例:
F
(
X
)
=
m
i
n
F
(
X
−
2
)
,
F
(
X
−
5
)
,
F
(
X
−
7
)
F(X) = min{F(X-2), F(X-5), F(X-7)}
F(X)=minF(X−2),F(X−5),F(X−7)
4. dp组成部分三:初始条件和边界情况(易错点)
5. dp组成部分四:计算顺序(易错点)
从已经计算的部分,根据转移方程推出后面的部分。
6. 动态规划的实现方式(模板技巧)
- 数组递推:少数题目只能使用这种方式实现
- 记忆化搜索:绝大部分可以使用这两种方式实现,且记忆化dfs往往思路更直接。记忆化搜索的可变参数数量决定dp数组的维度,每个可变参数的范围就是dp数组各个维度对应的大小。
// 使用记忆化搜索实现dp
class Solution {
public:
int dp[505][505]; // 使用C语言风格在外部定义dp数组,维度与dfs可变参数个数一致
int mod = (int)1e9+7;
int numWays(int steps, int arrLen) {
//对dp数组进行初始化
memset(dp, -1, sizeof(dp));
return dfs(steps, 0, arrLen);
}
int dfs(int steps, int loc, int arrLen){
// 对边界值进行判断
if (steps < loc || steps < 0 || loc < 0)
return 0;
if (steps == loc)
return 1;
// dp记录值检测
if (dp[steps][loc] != -1)
return dp[steps][loc];
// 递推公式实现
dp[steps][loc] = dfs(steps - 1, loc, arrLen) % mod;
if (loc > 0)
dp[steps][loc] += dfs(steps-1, loc-1, arrLen) % mod;
dp[steps][loc] %= mod;
if (loc < arrLen - 1)
dp[steps][loc] += dfs(steps-1, loc+1, arrLen) % mod;
dp[steps][loc] %= mod;
return dp[steps][loc];
}
};