代码随想录27期|Python|Day40|​动态规划|背包问题 二维 ​|​背包问题 一维| 416. 分割等和子集

46. 携带研究材料(第六期模拟笔试)

背包问题(二维dp数组)

 其中最基础的就是0-1背包问题,也就是每个物品实际上只有1个,只有取与不取两种状态。所以理论上来讲,可以通过暴力+判断的办法穷举所有可能性。

更进一步的,如果考虑基于前一次操作(放入物品)和状态(背包剩余容量),就可以给出基于动态规划的解法,因为实际上就是在做递推。

1、dp数组初始化。

初始化需要解决2个问题,意思就是确定下标和值的含义。本题涉及到两种下标,背包的剩余容量和物品是否放进去。值就是背包当前的最大价值。不难初始化如下图所示:

其中,dp由一个二维数组构成,其中行为物品编号,列为背包剩余容量,值为当前确定的最大价值。

初始化。此时的数组只能根据第一个值进行初始化。也就是:

(1)第一列j=0,此时背包剩余容量为0,放不了任何物品,价值dp = 0;

(2)根据第1个物品(行=0)初始化第一行:判断如果背包容量>=物品0的重量,则后面列都是value[0],也就是都是第一个物品的价值。

2、确定递推公式

假设当前的数组为dp[i][j],想求最大的价值,则需参考前一步的价值进行判断:

(1)之前说了,对于每一个物品都有取和不取两种操作。对于不取第i个物品,则当前的背包容量和价值维持上一个物品操作后的dp状态和值,也就是dp[i-1][j]。对应的是当前行的上一行、同一列的dp值:

(2)还有一种情况是,取了当前的物品,那么此时背包的容量和值都会发生更改。假设上一次的值为dp[i-1][j-weight[i]],其中j-weight[i]表示还没取当前物品的背包剩余空间,也就对应的是上一行,j-weight[i]列的值:

综上即可得出递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])。

3、确定遍历顺序

由于当前的遍历顺序都是先便利行(物品)再便利列(背包容量)的,所以先i后j。

4、更新dp。

根据之前确定好的递归公式确定更新后的dp数组。

n, weight, val = [int(x) for x in (input().split())], [int(x) for x in (input().split())], [int(x) for x in (input().split())]
num, bagweight = n[0], n[1]

dp = [[0]* (bagweight+1) for _ in range(num)]  # bagweight需要从0开始

for j in range(weight[0], bagweight+1):
    dp[0][j] = val[0]

for i in range(1, num):
    for j in range(1, bagweight+1):
        if j >= weight[i]:
            dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+val[i])
        else:
            dp[i][j] = dp[i-1][j]

print(dp[num-1][bagweight])

背包问题(一维dp数组)

如果观察二维的数组的更新过程可以发现,下一层的数组完全取决于上一层的数组的值,跟其他的位置没有关系。所以在更新下一行的数组的时候,可以直接把上一行的数组拷贝下来。每一步和上面二维数组的有一些区别。

1、确定dp数组的含义

dp数组的每一个dp[j]代表当前容量的最大价值。

2、确定递推公式

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),相当于把i的维度压缩了。

3、初始化

由于递推公式仅取决于当前物品,且取最大值,所以不用担心初始化的问题,全部设置为0就好。

4、确定遍历顺序

和之前二维数组从小到大遍历背包顺序不同,一维数组需要从大到小遍历背包容量。

num, bagweight = map(int, input().split())
weight = [int(x) for x in input().split()]
value = [int(x) for x in input().split()]

dp = [0] * (bagweight + 1)

for i in range(num):
    for j in range(bagweight, -1, -1):  # 注意是从大到小遍历背包空间
        if j >= weight[i]:
            dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
        else:
            continue
        
print(dp[-1])

416. 分割等和子集

本题在原来0-1背包的基础上做了一些变化:

1、没有给出背包容量。背包容量实际上就是集合元素之和除以2的target,如果能够恰好装满这个容量,那么就说明能够实现分割;

2、没有给出物品的重量和价值。实际上数字的重量和价值都等于数字本身;

3、初始化。由于都是正整数,且更新dp采用的是max操作,所以最好初始化为0.

采用一维dp进行求解,注意内层j遍历从大到小。

1、确定dp数组大小和含义。大小由target = sum /2 确定,相当于是背包的容量。dp的值就相当于是当前的数字和;

2、初始化。dp由于全部都是正整数,所以初始化为0即可;

3、确定递推公式。在0-1背包的一维数组解法中将weight和value统一成nums的值即可。

4、确定遍历顺序。两层,外层i遍历nums,内层j从大到小从target到nums[i]遍历背包容量。

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        if sum(nums) % 2 == 1:
            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])  # nums[i]看作重量和价值相等
        return True if dp[-1] == target else False

第40天完结!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值