代码随想录|Day35|动态规划04|01背包(二维、一维)、416.分割等和子集

01背包(二维dp数组)

背包最大重量为4。

物品为:

重量价值
物品0115
物品1320
物品2430

背包能背的物品最大价值是?

动规五步曲:

  1. dp数组的含义:dp[i][j] 表示从下标为 [0 - i] 的物品里任取,放入容量为 j 的背包,价值总和最大是多少。(如下图所示)
  2. 递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])。对于每个物品,都有放于不放2种选择。如果不放第 i 件物品,dp[i][j] = dp[i - 1][j]。如果放第 i 件物品,dp[i][j] = dp[i - 1][j - weight[i]] + value[i](需要确保背包容量足够,否则 j - weight[i] 为负)
  3. 初始化:根据递推公式,dp[i][j] 由左上角推出,我们应该初始化第一行 dp[0][j] 和第一列 dp[i][0]dp[0][j] 容量小于 物品0重量的时候为0,大于等于 物品0重量的时候为物品0重量。dp[i][0] 表示背包容量为 0,无法放入任何物品,因此 dp[i][0] = 0
  4. 遍历顺序:先遍历物品还是先遍历背包都是可以的。
  5. 打印dp数组

 

def test_2_wei_bag_problem1():
    weight = [1, 3, 4]  # 物品的重量列表
    value = [15, 20, 30]  # 对应物品的价值列表
    bagweight = 4  # 背包的最大容量

    # 初始化动态规划表,行数为物品数量,列数为背包容量+1(从0到bagweight)
    # dp[i][j]表示在前i件物品中,对于容量为j的背包,可以获得的最大价值
    dp = [[0] * (bagweight + 1) for _ in range(len(weight))]

    # 初始化第一行,仅考虑第一件物品时的情况
    # 当背包容量大于等于第一件物品的重量时,可以选择放入这件物品
    for j in range(weight[0], bagweight + 1):
        dp[0][j] = value[0]

    # 逐个考虑剩下的物品
    for i in range(1, len(weight)):  # 遍历物品
        for j in range(bagweight + 1):  # 遍历背包容量
            # 如果当前背包容量小于物品i的重量,则物品i不能加入背包
            # 此时,最大价值与不考虑物品i时相同
            if j < weight[i]:
                dp[i][j] = dp[i - 1][j]
            # 否则,需要决定是加入物品i还是不加入
            # 加入物品i的情况下,最大价值为加入物品i之前的最大价值加上物品i的价值
            # 不加入物品i的情况下,最大价值与不考虑物品i时相同
            # 取两者的较大值
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

    # 输出最终结果:所有物品考虑完毕后,容量为bagweight的背包可以获得的最大价值
    print(dp[len(weight) - 1][bagweight])

01背包(一维dp数组)

 使用一维数组时,数组的每个元素dp[j]表示对于当前考虑的物品,容量为j的背包所能装入的最大价值。关键在于更新这个数组时需要从后往前更新,这样可以保证在更新dp[j]时,dp[j-weight[i]]仍然代表没有考虑当前物品时的状态。

动规五步曲:

  1. dp数组的含义:dp[j] 表示容量为 j 的背包,价值总和最大是多少。
  2. 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
  3. 初始化:dp[0] = 0,因为背包容量为0所背的物品的最大价值就是0。其他下标应该初始化多少呢?看一下递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了
  4. 遍历顺序:倒序遍历,01背包问题由于每个物品只有一个,正序遍历会导致同一个物品被多次加入背包。
  5. 打印dp数组

def test_2_wei_bag_problem1_optimized():
    weight = [1, 3, 4]
    value = [15, 20, 30]
    bagweight = 4

    # 初始化一维数组
    dp = [0] * (bagweight + 1)

    # 遍历物品
    for i in range(len(weight)):
        # 从后往前遍历背包容量
        # 注意这里的范围是 weight[i] 到 bagweight
        # 这是因为当背包容量小于物品重量时,该物品无法被选中
        for j in range(bagweight, weight[i]-1, -1):
            # 更新dp[j]
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

    print(dp[bagweight])

416.分割等和子集

思路:数组和的一半为我们的子集和 target,假想一个背包,容量为 target,向其中放入元素,元素的重量和价值都为其值。

动规五步曲:

  1. dp数组的含义:dp[j] 表示容量为 j 的背包,价值总和最大是多少。
  2. 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]), 物品i的重量和价值都是nums[i]
  3. 初始化:dp[0] = 0,从dp[j]的定义来看,首先dp[0]一定是0。如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
  4. 遍历顺序:倒序遍历,01背包问题由于每个物品只有一个,正序遍历会导致同一个物品被多次加入背包。
  5. 打印dp数组
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 如果数组的总和是奇数,直接返回False,因为无法分割成两个和相等的子集
        if sum(nums) % 2 != 0:
            return False
        
        # 计算目标和,即分割后每个子集应该达到的和
        target = sum(nums) // 2
        
        # 初始化动态规划数组,dp[j]存储的是能够通过选择数组nums中的若干个数,其总和最多能达到j的值
        dp = [0] * (target + 1)
        
        # 遍历每一个数字,尝试更新dp数组
        for num in nums:
            # 从后向前遍历,确保每个数字只被计算一次
            # 这里遍历到num是因为,当背包容量小于当前数字重量时,无法选择当前数字
            for j in range(target, num - 1, -1):
                # 更新dp[j],dp[j]是当前容量为j时能达到的最大和
                # 选择当前数字和不选择当前数字的最大值
                dp[j] = max(dp[j], dp[j - num] + num)
        
        # 如果dp数组的最后一个元素(即dp[target])等于目标和,说明可以分割成两个和相等的子集
        return dp[-1] == target
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 回溯(超时)
        total_sum = sum(nums)
        # 如果总和是奇数,则无法分割成两个和相等的子集
        if total_sum % 2 != 0:
            return False
        
        target = total_sum // 2
        
        def dfs(index, current_sum):
            # 如果当前子集的总和等于目标和,则找到了一个解
            if current_sum == target:
                return True
            # 如果当前和大于目标和或遍历完所有元素,则此路径不可行
            if current_sum > target or index == len(nums):
                return False
            # 尝试两种情况:将当前元素加入子集或不加入
            return dfs(index + 1, current_sum + nums[index]) or dfs(index + 1, current_sum)
        
        # 从第一个元素开始,当前子集总和为0
        return dfs(0, 0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值