研习代码 day36 | 动态规划——背包问题

文章讲述了0-1背包问题的分类、两种解法(二维dp数组和一维数组),以及如何将其转化为等和子集问题并使用动态规划求解。
摘要由CSDN通过智能技术生成

一、背包问题分类和解法

        1.1 背包问题的分类

图来源:代码随想录 

        1.2 0-1 背包问题的描述

        给定一个背包,它的容量为C。现在有一系列的物品i(i从1到N),每个物品都有一个重量w[i]和一个价值v[i]。我们需要在背包中选择一些物品放入,使得总重量不超过背包容量,同时总价值最大化。

        注:特点是每个物品要么放入背包(选中),要么不放入背包(不选中),即物品的选择是离散的。而且每个物品只有一件。

        1.3 0-1 背包问题的解法

        1.3.1 二维dp数组解法

        dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为 j 的背包,价值总和最大是多少。 
                                  dp = [[0] * (bagWeight + 1) for _ in range(len(weights))]

        递推关系: dp[i][j] = max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
                           dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

        初始化:首列全为0,首行根据情况进行赋值
                       for j in range(weights[0], bagWeight + 1):
                                dp[0][j] = values[0]

        举例递推递推是从左上方开始的,所以可以调换 for 语句顺序

                        for i in range(1, len(weights)): # 遍历物品
                                 for j in range(bagWeight + 1): # 遍历背包重量
                                         if j < weights[i]:
                                                 dp[i][j] = dp[i - 1][j]
                                        else:
                                                 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])

        返回结果:
                        return dp[len(weights) - 1][bagWeight]

        1.3.2 二维dp数组解法的代码
def bagProblem_01(weights, values, bagWeight):
    # 二维数组:dp[i][j] 放物品 0——i 在容量为 j 背包种的最大价值和
    dp = [[0] * (bagWeight + 1) for _ in range(len(weights))]

    # 递推关系:max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
    # dp[i][j] = max(dp[i-1][j] , dp[i-1][j-weights[i]]+values[i])

    # 初始化:在构建二维数组时就已经对所有位置初始化为 0 ,即已经完成第一列的赋值,还需第一行
    for j in range(weights[0], bagWeight + 1):
        dp[0][j] = values[0]

    # 递推是从左上方开始的,所以可以从左往右按层递推
    for i in range(1, len(weights)):  # 遍历物品
        for j in range(bagWeight + 1):  # 遍历背包重量
            if j < weights[i]:
                dp[i][j] = dp[i - 1][j]
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])

    return dp[len(weights) - 1][bagWeight]


if __name__ == "__main__":
    weight = [2, 2, 3, 1, 5, 2]
    value = [2, 3, 1, 5, 4, 3]
    bagweight = 1

    result = bagProblem_01(weight, value, bagweight)
    print(result)
        1.3.3 一维数组解法

        dp[j]的含义: 容量为 j 的背包所背的最大价值
                                  

        递推关系: dp[j] = max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
                           dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

        初始化:初始化一个最小且不影响其他元素的元素值
                       dp[0] = 0

        举例递推一维数组是从二维数组种凝结而来,递推是基于上一层的情况,所以 for 语句不能调换顺序;第二个 for 语句的遍历是倒序,因为正序会叠加多次之前的物品价值
                          for i in range(1, len(weights)):  # 遍历物品
                                for j in range(bagWeight,weights[i]-1,-1):  # 遍历背包重量
                                      dp[j] = max(dp[j], dp[j - weights[i]] + values[i])  

        返回结果:
                       return dp[bagWeight]

        1.3.4 一维dp数组解法的代码
def bagProblem_01(weights, values, bagWeight):
    # 一维数组dp[j]:容量为 j 的背包所背的最大价值
    dp = [0] * (bagWeight + 1)

    # 递推关系:max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
    # dp[j] = max(dp[j] , dp[j-weights[i]]+values[i])

    # 初始化:初始化一个最小且不影响其他元素的元素值
    dp[0] = 0

    # 一维数组是从二维数组种凝结而来,递推是基于上一层的情况,所以 for 语句不能调换顺序
    # 第二个 for 语句的遍历是倒序,因为正序会叠加多次之前的物品价值
    for i in range(1, len(weights)):  # 遍历物品
        for j in range(bagWeight,weights[i]-1,-1):  # 遍历背包重量
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])

    return dp[bagWeight]


if __name__ == "__main__":
    weight = [2, 2, 3, 1, 5, 2]
    value = [2, 3, 1, 5, 4, 3]
    bagweight = 1

    result = bagProblem_01(weight, value, bagweight)
    print(result)

二、分割等和子集

        2.1 题目

        给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

        2.2 题目链接

        416.分割等和子集

        2.3 解题思路和过程想法

        (1)解题思路

        回溯:不断寻找符合要求的集合 path

        动态规划:转变为 0-1 背包问题,即找到背包容量为 sum(nums)//2 的集合,物体 i 的重量即为 nums[i]

        (2)过程想法

        最先想到回溯法,但是提醒超时,转而替换为动态规划

        2.4 代码

        2.4.1 回溯(提示超时)
class Solution:
    def backTracing(self,nums,path,startIndex):
        # 递归出口:找到累加和为 sum(nums) // 2  的子集,修改结果值 
        if sum(path) == sum(nums) // 2:
            self.res = True
            return

        # 剪枝:如果当前累加和超过目标值的一半,直接结束
        if sum(path) > sum(nums) // 2:
            return 

        # 递归(回溯)
        for i in range(startIndex,len(nums)):
            path.append(nums[i])
            self.backTracing(nums,path,i+1)
            path.pop()

    def canPartition(self, nums: List[int]) -> bool:
        # 剪枝:如果集合的累加和不是偶数,则直接返回结果
        if sum(nums) % 2 != 0:
            return False

        self.res = False

        self.backTracing(nums,[],0)

        return self.res
        2.4.1 动态规划
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 找到背包容量为 sum(nums)//2
        if sum(nums) % 2 != 0:
            return False

        target = sum(nums) // 2
        # 一维数组
        dp = [0] * (target + 1)

        for i in range(len(nums)):              # 遍历物体
            for j in range(target,nums[i]-1,-1):    # 遍历重量(数字)
                dp[j] = max(dp[j], dp[j-nums[i]]+nums[i])

        return dp[-1] == target
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值