初级背包问题,层层剖析为什么这样做。最好需要自己推演一遍。

初级背包问题

初级背包问题分为两种,01背包,和完全背包,两者的区别主要是在于,01背包一个物品只能选一次,完全背包则是同一个物品可以选择多次。

01背包

这里用力扣经典例题为例:

力扣416 分割等和子集为例: 416. 分割等和子集

相关企业

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

示例 1:

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

示例 2:

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

按照题意可总结题目要求,从数组里面几个值,使其和恰好为数组总和的一半,则满足分割子集的要求。

则把题目简化为 取其中几个数使其和为target,这里的target为sum(nums)的一半

我们这里把题目抽象成一个背包背景下的问题,方便初学者理解,如下:

能最多能承载 target kg 的背包,需要从数组里面选合适重量的数,并将其装入背包里面。

由题可知,我们需要的是和为 target的几个数,此时,target就可以抽象为 背包的最大容量,我们要尽量把背包装满,刚好能装满就刚好满足题意。不能装满也是一种最接近装满的状态。

现在我们来看代码

python 复制代码
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total%2 != 0:
            return False
        target = total // 2
        dp = [0]*(target+1)
        for num in nums:
            for j in range(target,num-1,-1):
                dp[j] = max(dp[j],dp[j-num]
                +num)
                
        return dp[target] == target
        
        

这里无法理解的话,最好自己做一个手动递推。

下面我们来解释代码,整个过程可以抽象为往背包里面放物品的过程。

  • 初始化dp数组,其含义最大承重为i的背包,在当前情况下,最多能放下多重的物品。所以我们只需要全部初始化为0即可。
  • 现在,我们可以想象这样一个情景,当按一定顺序发放一定质量的物品,你可以选择放入自己的背包或者不放入自己的背包。
  • 同时第二个循环,我们采取的是倒序遍历,为什么呢,我们假设是正序遍历,我们自己在推演的过程中会发现,之前的物品都被多加了不再限定在0或者1 之间。
  • 同时这里解释为什么是倒序遍历呢?我们这里维护一个固定载重的背包下,当前的限制条件下最大能放的质量,我们通过前一个状态,也就是dpj-num,一定是上一轮已经维护好的能放最多的情况,所以我们与自己当前的情况相比,要不要选择接受这一样物品,从而维护自己达到当前轮的最好状态。
  • 基本思路如上,想要真正理解更需要自己一步步在实际过程中去推演。

完全背包

完全背包例题如:518 零钱兑换 二 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

ini 复制代码
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

ini 复制代码
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

ini 复制代码
输入: amount = 10, coins = [10] 
输出: 1
python 复制代码
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        #注意完全背包 一维dp的时候,第二个循环是正序的
        #任选几个数和为 amount
        counts = 0
        dp = [0]*(amount+1)
        dp[0] = 1
        #定义dp数组 dp[j] 在j的容量下,有几种方法
        for coin in coins:
            for j in range(coin,amount+1):
                dp[j]+= dp[j-coin]
        return dp[amount]
        #TODO:
        #非常重要的问题,为什么这样子初始化???答:当容量为0 的时候 方案数为1,就是什么都不选。

这里主要解释为什么是正序?

当dp数组全部

假设 amount = 5, coins = 1, 2, 5

1 0 0 0 0 0 (dp数组的初始状态)

当前我们取coins下标为0 的数,值为1,

我们采用正序遍历,可得过程为:

取 1

1 1 1 1 1 1

取2 1 1 2 2 3 3

取5

1 1 2 2 3 4

正序我们取当前的数,先行修改,将前面的组合数修改到最新,我们只需要考虑 dp4-2 的值,而非 dp4-2-2,因为dp2中已经更新了dpi-2

下面我们再看个有关逆思维的问题。

\[\]: leetcode.cn/problems/in...

题解:3592. 硬币面值还原 - 力扣(LeetCode)

很多地方我感觉无法表述的很清楚,请同学们见谅,自己可以推演一下,理解会更深刻哦。

相关推荐
倒头就睡的小比特1 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!1 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼1 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光1 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark1 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her11 天前
并查集-听课笔记
笔记·算法·并查集
码流子1 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven1 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark1 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218611 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法