代码随想录算法训练营day42 | 01背包问题(一),01背包问题(二), 416. 分割等和子集

博客介绍了动态规划在解决01背包问题和分割等和子集问题中的应用,详细阐述了动规五部曲,并解释了二维数组和滚动数组的概念。通过实例分析了dp数组的初始化、递推公式以及遍历顺序,同时提供了相关问题的思考和解答。
摘要由CSDN通过智能技术生成

理论基础:二维数组

  • 《算法图解》第九章背包的讲解更好的帮助理解

动规五部曲:

  1. 确定dp数组以及下标的含义:使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

  2. 确定递推公式:关键是在不放与放物品i的两个状态中取价值更大的那一种。            dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
  3. dp数组如何初始化:dp[i][0] = 0, dp[0][j]; 对于dp[0][j]分两种情况,一是 j < weight[0]的时候,dp[0][j] = 0,二是当j >= weight[0]时,dp[0][j] 应该是value[0]。那么其他的部分则可以初始化为任意值因为都会被更新。
  4. 确定遍历顺序:先遍历 物品还是先遍历背包重量呢?其实都可以!! 但是先遍历物品后遍历背包更好理解。(既然二维数组已经定义了,那么就按照默认的先行后列来遍历)
  5. 举例推导dp数组

Q:对于递推公式,难道放还会比不放得到的价值更低吗?

A:状态1: 选择放实际上对前面的操作是有要求的,需要有足够的重量空间来放入,甚至要挤掉某个物品;状态2: 选择不放此时就需要前面的操作实现价值最大化。这样就需要对比两种状态下哪个得到的价值更大。

理论基础:滚动数组(一维数组)

Q:如何实现的降维?

A:其实就是只使用一行(将一个二维矩阵压缩成一行的数组),每次都更新这一行的数据(前提是当前行是由上一行推导得来),因此也就没有i了,此时i=0了。

(说白了,最终要的结果在右下角,那么其余位置的值都是为了推导出这个结果,所以不需要保存下来,使用一次直接舍弃就好,因此可以降维)

动规五部曲:

  1. 确定dp数组的定义:dp[j]表示容量为j的背包,所背的物品价值可以最大为dp[j]
  2. 一维dp数组的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
  3. 一维dp数组如何初始化:dp[0] = 0; 其余的要初始化成一个非负数的最小值,这样才能实现求max的更新,那么就都初始化为0。
  4. 一维dp数组遍历顺序:从后先前(背包容量由大到小)
  5. 举例推导dp数组

Q:为什么遍历顺序是后->前?

A:因为在二维数组的情况时,当前值是由正上方和左上方的值推导得来,每一层的数据都是隔离的。那么降到一维数组时,就只有左方的值会影响当前值。如果还按照从前向后的顺序,每次更新后的值依然会对后续值产生影响。为了避免这个问题,所以需要从后向前遍历。

下面附上视频留言的帮助理解:

  • “列表后面的值需要通过与前面的值比较确定,因此要先处理”
  • 01背包每个背包都只有一个,这个包放与不放与上一个包的状态有关,而左边的数组是描述上一个包状态的量,右边的数是当前包状态的一个待定,代码里就是右边数组的确定需要左边的数,所以在计算出右边的数之前不能破坏左边的数;

测试代码参考网站:代码随想录

416. 分割等和子集(medium)

注意:

  • 一个商品如果可以重复多次放入是完全背包问题,而只能放入一次则是01背包问题

本题经过分析得出主要意思是:

  • 判断集合里能否出现总和为 sum / 2 的子集

只有确定了如下四点,才能把01背包问题套到本题上来。

  • 背包的体积为sum / 2
  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。

动规五部曲:

  1. 确定dp数组以及下标的含义:dp[j]表示背包总容量是j,最大可以凑成j的子集总和为dp[j]
  2. 确定递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
  3. dp数组如何初始化: 首先dp[0]一定是0; 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。(因此为了统一,可以直接将非0下标都初始化为负无穷,自己测试。。。)
  4. 确定遍历顺序:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!(其实无论使用二维还是一维数组都先遍历物品就好)
  5. 举例推导dp数组:dp[j]的数值一定是小于等于j的(因为就这道题而言,物品i的重量是nums[i],其价值也是nums[i],因此j就代表了背包的容量,是上限)。如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。
class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        if sum(nums) % 2 != 0:
            return False
        target = sum(nums) // 2
        #step1: dp[j] 表示: 容量为j的背包,所背的物品价值可以最大为dp[j]
        #step2: dp[j] = max(dp[j], dp[j-nums[i]]+nums[i])
        #step3: 初始化:dp[0]=0, 由于nums数组内都是非负整数,那么其余的都初始化为0即可
        dp = [0]*10001 #200*100 // 2 + 1
        #step4: 遍历顺序:滚动数组要用倒序遍历,且先物品后背包
        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])
        #step5: 打印检查
        return target == dp[target]

相似题目:(之后再做。。。)

  • 698.划分为k个相等的子集
  • 473.火柴拼正方形

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值