近期刷题刷到了动态规划,在背包问题这感觉有些困难,记录一下。
首先是0-1背包,二维数组dp[i][j]表示选取物品为0-i,背包容量为j的背包最大能装的价值,
dp[i][j] = max(dp[i][j], dp[i-1][j-w(i)] + v(i))
,也就是说,可以选物品i也可以不选,取价值较大的那个,因为后面的数据依赖于前面一行的数据,所以遍历顺序是从前往后还是从后往前是无所谓的,至于是先遍历物品还是先遍历背包容量也是无所谓的,因为每个数据依赖的数据在它左上角方向,所以不管是先哪个都是可以的。
其次是0-1背包的一维数组,我们发现二维数组里面每次遍历的时候用到的也只是上面一行数据,所以可以把二维数组压缩成一维,每次更新这一行的数据就行了。
那么dp[j]
用来表示选取物品为0-i,背包容量为j的最大价值,dp[j] = max(dp[j], dp[j-w(i)] + v(i))
依然是物品i可选可不选,取价值大的那个即可,可以看到每个数据是依赖于它前面的数据的,所以还从前往后遍历可以吗?不可以!
因为它依赖的数据属于是上一个物品i的那一行数据,也就是我们在二维数组里面的前一行的数据,但是现在变成了一维数组,如果从前往后遍历, 那么上一行的数据就被覆盖掉了,它对应的是前面的物品被多次选取了,所以只能从后往前遍历。
那先遍历背包容量还是先遍历物品呢?答案是先遍历物品,我们考虑先遍历背包容量会出现什么问题:对于每容量它只能从后往前遍历,再依次遍历物品,因为容量是先遍历大的再遍历小的,那么小容量背包是没有被更新的,其实相当于包里面只能装一个物品了,也是不可行的。
再往后,到了完全背包问题,也就是一个物品可以被多次选取。
那么它和0-1背包的区别就只有能不能多次选取同一个物品这一点,我们只需要把遍历的顺序改为从前往后就可以了。
对于完全背包本身来说,它是只能先遍历物品再遍历容量的,而且要从前往后遍历。
但是对于它的变体问题,比如说求排列问题有多少种排列方式,因为我们的dp数组这时候已经不是最大容量了,而是排列方式数目,所以就要先遍历容量,这样的话可以先加入小的数和先加入大的数的方案都会被放进来,因为数可以重复选,所以也是从前往后遍历;
对于求组合问题,因为先加入大的数和先加入小的数是一种方案,我们就要先遍历物品再遍历容量,这样就不会出现重复,因为可以重复选,所以也是从前往后遍历。
总结一下,对于背包问题来说,物品能不能重复选,决定了背包容量这层for循环里面是从前往后遍历还是从后往前遍历;对于完全背包问题来说,求排列之类的问题有多少种方案就要先遍历容量,求组合之类的问题有多少种方案就要先遍历物品,其他的类似于求最小几个数可以组成目标数之类的题,用排列还是组合都可以的话,就无所谓先遍历哪个。