一、01背包问题 二维
1. 具体思路
(1)dp数组的含义:dp[ i ][ j ]二维数组表示下表[0, i]之间的物品任取放入容量为 j 的背包里。
(2)递推公式:dp[ i ][ j ]取决于是否放当前的物品 i ,如果不放物品 i ,那么当前状态的最大价值为dp[i-1][j];如果放物品 i ,那么当前状态为不放物品i,并且背包的容量也减去物品i的容量之后所能放的最大价值,再加上物品i的价值。因为当前要求最大价值,所以取最大值。dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i])
(3)dp数组初始化:通过画图找到递推公式的变化方向,可以发现递推公式的变化方向分别来自上方和对角线,所以通过初始化最上方和最左边,对角线就可以推导出来。
dp数组初始化需要明确dp数组的含义。例如初始化最左边,它的含义是背包容量为0,放物品0、1、2、...、j的最大价值是多少,显然最大价值为0,初始化为0,因为背包容量为0。初始化最上方,它的含义是背包容量为0、1、2、...、i分别放物品0的最大价值是多少,物品0的容量为多少,前面的值为0(因为放不下),而后面的值全部初始化为物品0的价值。因此用0初始化最左列,用物品0的价值初始化最上面那一行。
非0下标应该初始化为多少:由于其他位置都是由递推公式(前一个状态)得到的,所以初始化为任意值均可以。
(4)遍历顺序:两层for循环,一个for遍历物品,一个遍历容量。本题先遍历物品,还是遍历背包对于二维实现均可以。
(5)打印数组 。
2. 代码实现
n, bagweight = map(int, input().split())
weight = list(map(int, input().split()))
value = list(map(int, input().split()))
dp = [[0] * (bagweight + 1) for _ in range(n)]
for j in range(weight[0], bagweight + 1):
dp[0][j] = value[0]
for i in range(1, n):
for j in range(bagweight + 1):
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])
print(dp[n - 1][bagweight])
二、01背包问题 一维
1. 具体思路
(1)dp数组的含义:dp[j]表示容量为j的背包所背的最大价值为dp[j]。
(2)递推公式:由于一维数组解法的本质是滚动数组,所以不放物品i的当前层为dp[j](此时的dp[j]尚未更新,所以拷贝上一层数据的值)。如果放物品[i],那么减去i的重量,加上i的价值。然后二者取最大值max即可。
(3)dp数组初始化:背包容量为0,那么最大价值为0,所以dp[0] = 0。非0下标初始化为非0最小值(1.不能小于0;2.由于需要比较max大小),因此初始化为0。
(4)遍历顺序:a. 倒序遍历背包:为了防止一个物品添加多次所以只能使用倒序遍历。b. 为什么先遍历物品再遍历背包,不可颠倒:因为遍历倒序背包,如果颠倒两个嵌套,那么只会遍历一个物品。
(5)打印dp数组。
2. 代码实现
n, bagweight = map(int, input().split())
weight = list(map(int, input().split()))
value = list(map(int, input().split()))
dp = [0] * (bagweight + 1) # 创建一个动态规划数组dp,初始值为0
dp[0] = 0 # 初始化dp[0] = 0,背包容量为0,价值最大为0
for i in range(n): # 应该先遍历物品,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品
for j in range(bagweight, weight[i]-1, -1): # 倒序遍历背包容量是为了保证物品i只被放入一次
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
print(dp[bagweight])
三、416. 分割等和子集
本题是 01背包的应用类题目
1. 具体思路
整体思路:将传入的数组计算总和并且除以2,将该题抽象为一个背包问题,也就是问一个背包能不能装满数组总和除以2的物品,如果可以就return true。注意本题要求每个元素只能使用一次。
(1)定义dp数组的含义:容量为j的背包它所背的最大价值为dp[j],本题中每个元素既是它的总量也是它的价值。因此如果dp[target] == target,那么背包就被装满了。target等于所有元素相加除以2。
(2)递推公式:dp[j] = max(dp[j], dp[j-nums[i]]+nums[i]),因为此处重量和价值是相同的。
(3)dp数组初始化:dp[0] = 0,其他非0 下标初始化为非0整数的最小值,因此也初始化为0,所以全初始化为0。
(4)遍历顺序:先遍历物品,再遍历背包(因为每个物品只能使用一次所以需要倒序遍历)。
(5)打印dp数组。
2. 代码实现
class Solution:
def canPartition(self, nums: List[int]) -> bool:
sum = 0
target = 0
for i in range(len(nums)):
sum+=nums[i]
if sum % 2 == 0:
target = sum // 2
else:
return False
# dp[j] 表示容量为 j 的背包能装的最大价值(这里价值和重量均为数值本身)
dp = [0] * (target+1)
for num in nums:
for j in range(target, num-1, -1):
dp[j] = max(dp[j], dp[j-num]+num)
return dp[-1] == target