5.15 动态规划todo
- 简要介绍
1.1 目的:动态规划主要处理求最值的题型
1.2 核心问题:穷举,列出状态转移方程、判断算法问题是否具备【最优子结构】(通过子问题的最值得到原问题的最值)、是否存在【重叠子问题】(用暴力穷举的话效率会很低,可以使用【备忘录】和【DP table】来优化穷举问题)
2 如何写状态转移方程
2.1 明确【状态】->明确【选择】->定义dp数组/函数的含义
2.2 形成如下框架
自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
for 选择 in 所有可能的选择:
# 此时的状态已经因为做了选择而改变
result = 求最值(result, dp(状态1, 状态2, ...))
return result
自底向上迭代的动态规划
初始化 base case
dp[0][0][...] = base case
进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 求最值(选择1,选择2...)
2.3 例子
2.3.1 斐波那契数列
// f(n) 计算第 n 个斐波那契数
int fib(int n) {
// base case
if (n == 0 || n == 1){
return n;
}
return fib(n - 1) + fib(n - 2);
}
这个算法中算了很多次重复的内容,比如在算f(20)需要算f(19)和f(18),而算f(19)也要算f(18),在这个过程中就会产生不必要的计算
解决方法:【备忘录】算出一个子问题的值后就把它存到备忘录里,每次遇到一个子问题,先不着急求解,而是先去备忘录里查一下,如果发现已经解决过这个问题,直接把答案拿来用
【备忘录】自顶向下的策略
解决方法2:【DP table】int fib(int n) {
if (n == 0 || n == 1) {
return n;
}
// dp table
int[] dp = new int[n + 1];
// base case
dp[0] = 0; dp[1] = 1;
// 状态转移
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
2.4 写出状态转移方程就相当于写出了暴力解法
f(n)的函数参数会不断变化,把n当作状态,而这个状态是由前面的状态相加得来(n-1和n-2)
2.5 最优子结构需满足子问题间必须互相独立
2.6 如何列出状态转移方程
2.6.1 确定【状态】,原问题和子问题中会变化的变量
2.6.2 确定【选择】,导致【状态】产生变化的原因
2.6.2 明确dp函数的定义
3 动态规划的通用技巧:数学归纳思想
3.1 类比:数学归纳法:对k<n成立,思考对k=n是否成立
状态转移方程;假设dp[0……k-1]都已经被计算出来,然后思考怎么算出dp[i]
****总结:明确dp数组的定义;根据dp数组的定义,运用数学归纳法的思想

浙公网安备 33010602011771号