DP_bag

背包分类

01 背包

题目

有 n 件物品和一个最多能装重量为 w 的背包。第 i 件物品的重量是 weight[i],得到的价值是 value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大

首先想到回溯

  • 每一件物品只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是 O(2^n),这里的 n 表示物品数量

DP 解法

dp 定义

dp[i][j]表示从下标为[0-i]的物品里任意取,放进容量为 j 的背包,最大价值是多少

递推公式

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

base case

  • dp[i][0]

  • dp[0][j] (当 j < weight[0]的时候,dp[0][j] 是 0,因为背包容量比编号 0 的物品重量还小

    当 j >= weight[0]时,dp[0][j] 应该是 value[0],因为背包容量放足够放编号 0 物品)

  • 初始化代码

  • 最终初始化 dp 如图

遍历顺序

  • 先遍历物品,再遍历背包

  • 先遍历背包,再遍历物品

  • 最终 dp 结果

完整代码

状态压缩

dp 定义

dp[j]表示:容量为 j 的背包,所背的最大物品价值为 dp[j]

递推公式

dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

初始化

dp[0] = 0, 其它根据递推公式初始化成 0 以防止被覆盖

==遍历顺序==

  1. 背包倒序遍历,保证物品 i 只被放入一次

  2. 为什么二维 dp 数组历的时候不能倒序

    对于二维 dp,dp[i][j]都是通过上一层即 dp[i - 1][j]计算而来,只有前面的推导出来了才能正确推导出后面的 dp 数值,并且本层的 dp[i][j]并不会被覆盖

  3. 先遍历背包容量嵌套遍历物品

    滚动行没有更新完,即还未经过计算,直接被覆盖了

    倒序遍历本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖

完整代码

完全背包(每个物品无限次)

  • 在完全背包中,对于一维 dp 数组来说,循环顺序无所谓

    dp[j] 是根据 下标 j 之前所对应的 dp[j]计算出来的。 只要保证下标 j 之前的 dp[j]都是经过计算的就可以

  • 先遍历物品

  • 先遍历背包

  • 最终 dp 图

完整代码

总结

  • 01 背包二维物品、背包先后遍历顺序无所谓

  • 01 背包状态压缩版,必须先物品再背包

    • 背包倒序遍历,防止物品被放置多次

    • 防止下层数据被提前覆盖,即从右向左覆盖,本质是右下角的数据依赖上层左上角的数据

  • 完全背包状态压缩版,遍历顺序无所谓

  • 01 背包变体

    • 纯 01 背包:问装满这个容器的最大价值

    • 分割等和子集:问能不能装满

    • 最后一块石头:问最多能装多少

    • 目标和:装满这个背包有多少种方法: dp[j] += dp[j - nums[i]]

    • 一和零:装满背包(二维)最多有多少个物品:dp[i][j] = max(dp[i - x][j - y] + 1, dp[i][j])

  • 完全背包变体

    • 纯完全背包:问装满这个容器的最大价值

    • 装满这个背包有多少种方法

      • 组合数:先遍历物品再遍历背包(零钱兑换 2)

      • 排列数:先遍历背包再遍历物品(组合总和 4)

    • 装满这个背包最少用多少件物品(零钱兑换、完全平方数)

      • dp[j] = min(dp[j - coins[i]] + 1, dp[j])

      • 与先遍历物品还是背包无关

Last updated