● 01背包问题,你该了解这些!
● 01背包问题,你该了解这些! 滚动数组
● 416. 分割等和子集
理论基础
- 01背包
- 每种物品只有一个,只能用一次
- 完全背包
- 每种物品有无数个,可以能用无数次
01背包问题 二维
-
背包是可以暴力解决的
- 取或者不取当前物品 + 回溯实现
- 时间复杂度 O(2^n)
- 取或者不取当前物品 + 回溯实现
-
思路
-
dp数组以及下标的含义
//二维数组背包 dp[i][j] : 从下标为[0-i]的物品里面任意取,放进容量为j的背包,价值总和最大是多少
-
递推公式
dp[i][j] 不放物品i:dp[i-1][j] 放物品i:dp[i-1][j-weight[i]] + value[i] dp[i][j] = max( dp[i-1][j], dp[i-1][ j - weight[i]] + value[i] )
-
dp数组如何初始化
// 当前元素的上方,和左上方推出来的 // 第一行一定要初始化,第一列要初始化 第一列:容量是0,价值全为0 第一行:容量大于物品重量,值就是第一个物品的价值
-
遍历顺序
二维数组:先背包或先物品 for 无所谓
-
打印数组
-
-
题解
func main() { weight := []int{1, 3, 4} //物品重量 value := []int{15, 20, 30} //物品价值 test2DBagProblem(weight, value, 4) } // 二维数组解决 01背包 自带参数版本 // two dimensional array // params: // // weight 物品重量 // value 物品价值 // bagCapacity 背包容量 func test2DBagProblem(weight, value []int, bagCapacity int) int { totalNums := len(weight) //物品总数量 // define the dp := make([][]int, totalNums) for i := range dp { dp[i] = make([]int, bagCapacity+1) } // init array //init first column for i := 0; i < totalNums; i++ { dp[i][0] = 0 } //init first line for j := bagCapacity; j >= weight[0]; j++ { dp[0][j] = dp[0][j-weight[0]] + value[0] } // 递推公式, 先物品后背包 for i := 0; i < totalNums; i++ { for j := 0; j <= bagCapacity; j++ { if j < weight[i] { dp[i][j] = dp[i-1][j] } else { dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]) } } } return dp[totalNums-1][bagCapacity] } func max(a, b int) int { if a > b { return a } return b }
01背包问题 一维
-
类似上一题,只是说用每行都用上一行来覆盖计算,实现降维效果
- 变成滚动数组
- 需要满足的条件是上一层可以重复利用,直接拷贝到当前层
- 变成滚动数组
-
思路
-
dp数组以及下标的含义
dp[j] :表示容量为j的背包,所背物品价值最大为dp[j]
-
递推公式
dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
-
dp数组如何初始化
dp[0] = 0 // 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了
-
遍历顺序
- 一维数组必须有序,数据会循环使用,要避免数据污染
- 先正序遍历物品,再倒序遍历背包
- 正序遍历背包,物品存在重复计算
-
打印数组
-
-
题解
func main() { weight := []int{1, 3, 4} value := []int{15, 20, 30} test1DBagProblem(weight, value, 4) } func test1DBagProblem(weight, value []int, bagCapacity int) int { dp := make([]int, bagCapacity+1) for i := 0; i < len(weight); i++ { // 这里必须倒序,区别二维,因为二维dp保存了i的状态 for j := bagCapacity; j >= weight[i]; j-- { dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) } } return dp[bagCapacity] } func max(a, b int) int { if a > b { return a } return b }
416. 分割等和子集
关联 leetcode 416. 分割等和子集
本题是 01背包的应用类题目
-
思路
-
dp数组以及下标的含义
dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]
-
递推公式
dp[j] = max( dp[j], dp[j - nums[i]] + nums[i])
-
dp数组如何初始化
- dp[0]一定是0
- 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
- 这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了
-
遍历顺序
- 一维数组
- 先物品后背包,背包倒序
- 一维数组
-
打印数组
-
-
题解
func canPartition(nums []int) bool { sum := 0 for _, num := range nums { sum += num } //总和不是偶数, 则一定不能拆除两个元素相等的子集,直接退出 if sum%2 != 0 { return false } target := sum / 2 //求到一半就可以了 dp := make([]int, target+1) // 一维背包的递推公式 for _, num := range nums { for j := target; j >= num; j-- { if dp[j] < dp[j-num]+num {//省略 max() 比较 dp[j] = dp[j-num] + num } } } return dp[target] == target }
总结
- 01背包可用 一维 或 二维 背包解决
- 一维背包需要注意遍历顺序防止重复计算