茶艺师学算法打卡19:动态规划
学习笔记
对于我这茶艺师而已,递归是算法里的“大山”之一,而动态规划就是另外一座“大山”。
我遇到动态规划的题目,不发怵是不可能的。
里面有着众多的变形题型,由于存在着代码优化,直接背这些题目的代码反而还会坠入迷雾里。
真的只有熟练其解题思路与方法才能“以不变应万变”。
幸好,关于动态规划的解题思路与方法,也是有着众多门派,在里头,总有“一款属于你的”。
这里就用一道经典题 ACW2. 01背包问题 ,来展示2种不同思路吧。
题目说明
有 N
件物品和一个容量是 V
的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi ,价值是 wi 。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。
输入格式
第一行两个整数,N,V
,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi ,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
解法1
先写出其暴力做法,关注人力模拟时的做的决策,依此写出状态转移方程,最后就是确定边界与目标,递推实现。
这里很明显,用 表示“从前 i 个物品中选了总体积为 j 的物品放入背包,其价值的最大值”。
那么在选第 i 个物品时,就有两个决策:
- 决策1:不放第 i 个物品,那么
- 决策2:放第 i 个物品,那么状态则是从“没选第 i 个物品” 转移过来加上这第 i 个物品的价值 ,既
初值: , 其他位置负无穷
目标:
示例代码
f := make([][]int, n + 1)
for i := 0; i <= n; i++ {
f[i][] := make([]int, V + 1)
for j := 0; j <= V; j++ {
f[i][j] = -1<<31-1
}
}
f[0][0] = 0
for i := 1; i <= n; i++ {
for j := 0; j <= V; j++ {
f[i][j] = f[i - 1][j]
}
for j := v[i]; j <= V; j++{
f[i][j] = max(f[i][j] ,f[i - 1][j - v[i]] + w[i])
}
}
ans := 0
for j := 0; j <=V; j++ {
ans = max(ans, f[N][j])
}
解法2
直接看图
经过以上分析,我们得到状态转移方程
示例代码1
for i := 1; i <= n; i++ {
for j := 0; j <= m; j++ {
f[i][j] = f[i - 1][j]
if j >= v[i] {
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])
}
}
}
观察该方程,我们能发现:
- i 这一维,只用到 i - 1,这里就可以滚动数组,只保留上一层的数据,优化空间使用
- j 这一维,要么是 j,要么是 j - vi 这个比自己小的数,那么 j 就可以从大到小遍历
示例代码2
for i := 1; i <= n; i++ {
for j := m; j >= v[i]; j-- {
f[j] = max(f[j], f[j - v[i]] + w[i])
}
}