力扣上刷了快200道题了,动态规划的题也做了很多,但是发现自己始终不得其精髓,有些奇奇怪怪的题可以转化为动态规划问题,但是没有经过系统训练就是不知道,就算看到答案也是看不懂;
网上类似的讲解很多很多,我这里只是总结其他人的观点,再融入自己见解,主要目的是对学习过程进行记录,而不是搞出一些新的知识点;欢迎各位能在我理解的基础上更进一步,总结出自己的学习方法和思路;
经典题目
有容量为n的容器。有很多种物体,第i件物品对应的体积和价值分别是Vi及Wi,每种物品仅有一件,可以选择放或者不放,问将那些物体放入背包会使得总价值最大。
基本思路
根据动态规划一般思路,需要给出状态转移方程,这玩意儿感觉就跟数学里面的归纳法一样,就是需要给出一系列数字中后一个跟前一个关系;那么问题是这一系列数怎么选才能让他们有规则呢?
对于我这种数学成绩很差的人来说只能穷举了,一个一个看,一个一个总结。
如果对于前i-1件物品恰好放进一个容量为v的背包的最大价值是F[i-1][v],那么对于第i个物体,同样大小的背包,我们可以选择放或者不放。
如果放,那么
F
[
i
]
[
v
]
=
F
[
i
−
1
]
[
v
−
V
i
]
+
W
i
F[i][v]=F[i-1][v-Vi]+Wi
F[i][v]=F[i−1][v−Vi]+Wi
其中Vi代表第i个物体的体积,Wi代表第i个物体的价值,F[i-1][v-Vi]代表如果前i-1个物体中如果背包体积v-Vi情况下最大的价值
如果不放,那么
F
[
i
]
[
v
]
=
F
[
i
−
1
]
[
v
]
F[i][v]=F[i-1][v]
F[i][v]=F[i−1][v]
因此我们只需要取较大值就可以,可以写做
F
[
i
]
[
v
]
=
m
a
x
(
F
[
i
−
1
]
[
v
]
,
F
[
i
−
1
]
[
v
−
V
i
]
+
W
i
)
F[i][v]=max(F[i-1][v],F[i-1][v-Vi]+Wi)
F[i][v]=max(F[i−1][v],F[i−1][v−Vi]+Wi)
除此之外,在栈空间中声明的对象需要全部初始化为0,并没有什么额外的条件;
我们注意到上面其实是有依赖关系的,也就是说我们在找F[i][v]的时候必须事先知道F[i-1][v]及F[i-1][v-Vi],也就是对于大的i和v都依赖于更小的i和v,因此可以从0——max进行两层循环,伪代码如下:
空间优化
其实上面的过程一维数组就能实现,我们选择只保留背包大小这一个维度,那我们的状态转移方程就可以这样写:
F
[
v
]
=
m
a
x
(
F
[
v
]
,
F
[
v
−
V
i
]
+
W
i
)
F[v]=max(F[v],F[v-Vi]+Wi)
F[v]=max(F[v],F[v−Vi]+Wi)
上面的方程什么时候会与
F
[
i
]
[
v
]
=
m
a
x
(
F
[
i
−
1
]
[
v
]
,
F
[
i
−
1
]
[
v
−
V
i
]
+
W
i
)
F[i][v]=max(F[i-1][v],F[i-1][v-Vi]+Wi)
F[i][v]=max(F[i−1][v],F[i−1][v−Vi]+Wi)
等价呢? 当我们访问到的F[v]都是i-1对应的f[v]时,那我们就可以推出下一个时候的最大价值。
那我们怎么保证我们每次访问的都是i-1的呢,本来不同的i值对应一个值,现在同一个v下只用一个一个值来代表整个不同i对应的数组;我们只需要保留i-1时候,本来我们计算后面的i需要依赖前面的i值刷新;但是如果我们从大到小遍历v,那么当到i到时候,i-1对应的值并没有被刷新,也就达到了我们的目的;
这样伪代码如下:
变种题目
这里要求我们把一个数组分成两组使其和相等,我们可以先求和,然后判断是否为偶数,如果为偶数,那么我们可以这样想:
将和除以二得到的数记为targetnum;
背包的容量就是数组的长度;每个物体的体积就是1,价值就是对应的数,我们只需要按照01背包问题进行查找,看看能否找到放进去的物体价值刚好等于targetnum;如果有,则返回真,如果无则返回false;
放上链接,快去做一做吧:
https://leetcode-cn.com/problems/partition-equal-subset-sum/
https://leetcode-cn.com/problems/ones-and-zeroes/