Day 42 | 动态规划 04

85 阅读3分钟

01背包

有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值是value[i]

重点:每个物品只能用一次

暴力的时间复杂度:o(2n)o(2^n) 动规:o(n2)o(n^2)

二维dp数组

动规五部曲

  1. 确定dp数组以及下标的含义 dp[i][j] i:物品 j:背包容量
  2. 递推公式 dp[i][j] = max(dp[i-1][j-weight[i]]+value[i], dp[i-1][j]) 注意都是i-1 因为是从上一状态得到的
  3. 初始化 三种情况 dp[i][0]=0 dp[0][j]=value[0] else: dp[i][j]=0
  4. 确定遍历顺序 先遍历物品 再遍历背包 其实只需要左上角的状态
  5. 举例验证
def test_2_wei_bag_problem1(bag_size, weight, value) -> int:
	rows, cols = len(weight), bag_size + 1
	dp = [[0 for _ in range(cols)] for _ in range(rows)]

	# 初始化dp数组.
	for i in range(rows):
		dp[i][0] = 0
	first_item_weight, first_item_value = weight[0], value[0]
	for j in range(1, cols):
		if first_item_weight <= j:
			dp[0][j] = first_item_value

	# 更新dp数组: 先遍历物品, 再遍历背包.
        # len(weight), i.e. num of things
	for i in range(1, len(weight)):
		cur_weight, cur_val = weight[i], value[i]
		for j in range(1, cols):
                        # 要判断背包能不能装下当前物品
			if cur_weight > j: # 说明背包装不下当前物品.
				dp[i][j] = dp[i - 1][j] # 所以不装当前物品.
			else:
				# 定义dp数组: dp[i][j] 前i个物品里,放进容量为j的背包,价值总和最大是多少。
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cur_weight]+ cur_val)

	print(dp)
一维dp数组

背包问题,状态可以压缩

上一层可以重复利用,直接拷贝到当前层

即,把dp[i - 1]那一层拷贝到dp[i]上:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])

可以只遍历背包,通过背包重量是否改变说明是否放入

递推公式为:

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

推导的时候一定是取价值最大的数,所以需要从后往前遍历赋值

倒序遍历是为了保证物品i只被放入一次

而对于对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖

def test_1_wei_bag_problem():
    weight = [1, 3, 4]
    value = [15, 20, 30]
    bag_weight = 4
    # 初始化: 全为0
    dp = [0] * (bag_weight + 1)

    # 先遍历物品, 再遍历背包容量
    for i in range(len(weight)):
        # 背包从最大价值开始,但j - weight[i]需要大于等于0 因此注意遍历范围
        for j in range(bag_weight, weight[i] - 1, -1):
            # 递归公式
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

    print(dp)

一维dp数组的遍历范围、遍历顺序、内外层顺序很重要

416. 分割等和子集

def canPartition(self, nums: List[int]) -> bool:
    # 分割两个背包  背包容量 sum(nums)//2
    # 物品 len(nums)
    n = len(nums)
    s = sum(nums)
    if s % 2 != 0:
        return False
    weight = s // 2
    # 二维dp
    # dp = [[0] * (weight+1) for i in range(n)]
    # for j in range(weight+1):
    #     dp[0][j] = nums[0]
    # for i in range(n):
    #     for j in range(weight+1):
    #         if j >= nums[i]:
    #             dp[i][j] = max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i])
    #         else:
    #             dp[i][j] = dp[i-1][j]
    # 一维dp
    dp = [0] * (weight+1)
    for i in range(n):
        for j in range(weight,nums[i]-1,-1):
            dp[j] = max(dp[j],dp[j-nums[i]]+nums[i])
    # print(dp)
    return (dp[-1]==weight)