前言
开局先唠点嗑,无此雅意可掠过。
相信学算法的伙伴们都知道动态规划这一词汇。
但是常常碰到动态规划的题目,简直就是一脸懵逼,毫无思路。
当然我也不例外,初识动态规划的时候,看着题解的状态转移方程,是如此的简单,但是自己就是想不到。每每这种时候,脑袋里都会浮现这一句代码:sb.append(我)。
直到我看到了左神的视频,突觉”茅厕顿开“。有很多小伙伴在看到状态转移方程的时候,会不禁感叹,究竟是何许人也能想到如此nb的状态转移方程。但其实初出茅庐时动态规划的转移方程并不是那么容易想出来的,可以通过一步一步转换得来。当然天生就在智商方面氪金的神人便可另当别论。不过我们可以通过量变引起质变呀,通过一些题目来不断提高水平,慢慢的这类题目便可玩弄于指掌。
好戏开场
接下来用最经典的动态规划----01背包来,来展开从暴力递归到动态规划的神奇演变。
先上题为敬:
有n件物品和一个容量为bag的背包。
第i件物品的体积是c[i],价值是w[i],0<=i<N。求解将哪些物品装入背包可使价值总和最大。
上来就要你写出动态规划的状态转移方程,估计毫无头绪。那我们可以先想想怎么暴力的去解决这个问题。
暴力递归
思路:
对于每一件物品,我们都有两种选择,要或不要。那么就以这种思路先将暴力递归的解法写出来。
//为了代码更简洁,减少递归调用时书写的参数
//这里的w数组和v数组作为成员变量(c/c++为全局变量)
//决策第i件物品要还是不要,bag为当前背包容量
//调用:baoli(0,bag):从第一个物品开始,最初的背包容量
public static int baoli(int i, int bag) {
//边界条件:
//i==n说明没有物品可以选择了
//bag==0说明背包已经不能装东西了
if (i == n || bag == 0) return 0;
int yes = 0, no;
// 要第i件物品,前提是能装得下
if (bag - w[i] >= 0)
yes = v[i] + baoli(i + 1, bag - w[i]);//当前物品的价值+背包剩余的容量所能产生的价值
// 不要第i件物品
no = baoli(i + 1, bag);//对第i+1件物品做决策
// 当然则其较大者
return Math.max(yes, no);
}
暴力递归还是很简单的吧,非常贴近人类的思维。
观察暴力递归
接下来,观察暴力递归,会发现:
1) baoli(i,bag) 的值来源于v[i] + baoli(i + 1, bag - w[i]) 和 baoli(i + 1, bag)
2)当i==n或bag= =0的时候,baoli(i,bag)=0
有没有发现,这两个递归中的部分有点像动态规划中的预处理和状态转移方程呢。没错,就是这样。
所以,对于每个baoli(i,bag)所依赖的位置都是正下方和左下方的结果。
那么,结果就可以从下往上地推得出。至于每行从右往左还是从左往右,那就无所谓啦。
暴力递归的调用是baoli(0,bag),那么dp的结果就是dp[0][bag]
这么一来,预处理、状态转移方程和结果的位置不就出来了嘛。
接下来看代码
public static int dp(int n, int bag, int[] w, int[] v) {
int[][] dp = new int[n + 1][bag + 1];
//因为java创建的数组,所有元素的默认值都是0,所以预处理就可以免了
for (int i = n - 1; i >= 0; --i) {
for (int j = bag; j >= 0; --j) {
dp[i][j] = Math.max(
j - w[i] >= 0 ? v[i] + dp[i + 1][j - w[i]] : 0
dp[i + 1][j],
);
}
}
return dp[0][bag];
}
根据暴力递归,直接就把动态规划给改出来了。是否觉得眼前一亮。
当想不到动态规划的状态转移方程的时候,不妨想想暴力递归,然后通过暴力递归直接改成动态规划。以免直接想状态转移方程这个极具抽象的一个玩意。
在经过量变到质变之后,相信你也可以直接想出状态转移方程,只是现在初出茅庐,欠缺经验而已。
最终版
其实上面的动态规划还可以优化:
因为dp[i][j]所依赖的只是下方和左下方的结果,所以用一维的dp数组,从右到左地推就可以了。
public static int dp1(int n, int bag, int[] w, int[] v) {
int[] dp = new int[bag + 1];
for (int i = n - 1; i >= 0; --i) {
for (int j = bag; j - w[i] >= 0; --j) {
dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
}
}
return dp[bag];
}
每当动态规划没思路的时候,都可以尝试一下先想想暴力递归,尝试用暴力递归来引出状态转移方程。
最后附上左神的视频链接,视频中讲的更加细:暴力递归 --> 记忆化搜索 --> 动态规划
https://www.bilibili.com/video/BV1Ef4y1T7Qi?p=14&vd_source=454e419e241380a376eb0730483f6705