代码随想录算法训练营第四十四天|01 背包、动态规划:01背包理论基础(滚动数组)、416. 分割等和子集

01 背包

文档讲解:代码随想录

题目链接:46. 携带研究材料(第六期模拟笔试)

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

 暴力解法:每个物品都有放与不放两种状态,复杂度是2的n次方

动态规划:

dp[i][j] :下标为[0,i]之间的物品,任取放进容量为j的背包里的最大价值

必须是把dp数组定义为答案,然后找递推关系,反推到起点,然后双循环搞定

 dp[i][j]可以由哪几个方向推出来,当前背包的状态取决于放不放当前的物品i

不放物品i的最大价值:dp[i-1][j] 

放物品i:dp[i-1][j-weight[i]] + value[i]   

dp[i][j]  = max(dp[i-1][j] ,dp[i-1][j-weight[i]] + value[i]   )

dp数组初始化

初始化很有讲究

由 dp[i][j]  = max(dp[i-1][j] ,dp[i-1][j-weight[i]] + value[i]   )可以看出:dp[i][j]由它的左上方和正上方推出来的

所以要初始化第一行和第一列,dp[i][j]与它原来的值没有关联,所以初始化成什么值都可以

 遍历顺序

两层for循环,一个遍历物品,一个遍历背包的容量

对于二维数组实现的01背包,两层for循环是可以颠倒的,先遍历物品还是先遍历背包都可以

当前元素是由正上方和左上方推导出来的,如果要求当前元素的值,一定要确定左上方有值,正上方有值

如果先遍历物品,再遍历背包,那么就是一行一行的遍历,满足上方和左上方有数据,所以这个遍历方式是可以的

如果先遍历背包,再遍历物品,那么就是一列一列的遍历,满足上方和左上方有数据,所以这个遍历方式也是可以的

所以,对于二维数组实现的01背包,无论先遍历背包还是物品,要求的格子的值,满足左上方和正上方都有值,所以两种遍历方式都可以

def backpack(M,N,space,value):
    bp = [[0 for j in range(N+1)] for i in range(M)]
    #第一列都是0,初始化第一行
    for i in range(1,N+1):
        if space[0] > i:
            continue
        else:
            bp[0][i] = value[0]
    
    #先遍历物品,再遍历背包
    for i in range(1,M):
        for j in range(1,N+1):
            if space[i] <= j: #能放下i
            
                bp[i][j] = max(bp[i-1][j],bp[i-1][j-space[i]]+value[i])
            else:#不能放下i

                bp[i][j] =bp[i-1][j]
    return (bp[M-1][N])
    
M, N = map(int, input().split())
space = list(map(int, input().split()))
value = list(map(int, input().split()))

# 计算并输出结果
print(backpack(M, N, space, value))

注意:dp[i][j]  = max(dp[i-1][j] ,dp[i-1][j-weight[i]] + value[i]  ) 要保证[j-weight[i]]下标大于等于0才可以,也就是容量大于等于weight[i],也就是说,只有当容量大于i所占的空间时,才会考虑放不放i的问题,反之,那么肯定不会放物品i

动态规划:01背包理论基础(滚动数组)

用一维数组实现01背包

用二维数组实现01背包:dp[i][j]  = max(dp[i-1][j] ,dp[i-1][j-weight[i]] + value[i] ) 

可以看出当前层的数值是由上一层推导出来的,是不是就可以把i-1的数据拷贝到第i层,直接在第i层上进行操作,不断覆盖原来的数据

dp[j]:容量为j的背包所能装的物品的最大价值是dp[j]

递推公式

不放物品i的状态就是dp[j]

放物品i:dp[j-weight[i]] + value[i]

dp[j] = max(dp[j] ,dp[j-weight[i]] + value[i] ) 

初始化

dp[0] = 0

应该初始化一个非0中的最小值,这样取最大值不会覆盖递归出来的数据

遍历顺序

放物品i:dp[j-weight] + value[i]   这样写是因为在二维数组中dp[i-1][j-weight]中一定是没有物品i的,并且给物品i额外保留了位置,

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15  容量为1的背包中对于放不放物品0的判断

dp[2] = dp[2 - weight[0]] + value[0] = 30   容量为2的背包中对于放不放物品0的判断,但是dp[2 - weight[0]]之前已经判断过是否加入0背包了,可能是加入的,这就会造成重复

倒叙遍历保证每个物品被添加一次

不太懂

416. 分割等和子集

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

这是一个0、1背包问题

背包问题,大家都知道,有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

背包问题有多种背包方式,常见的有:01背包、完全背包、多重背包、分组背包和混合背包等等。

要注意题目描述中商品是不是可以重复放入。

即一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的。

要明确本题中我们要使用的是01背包,因为元素我们只能用一次。

回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。

那么来一一对应一下本题,看看背包问题如何来解决。

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

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

以上分析完,我们就可以套用01背包,来解决这个问题了。

重量和价值一样,当背包正好装满之后,最大价值就是11,那么就是true

递推公式

dp[j] = max(dp[j] ,dp[j-num[i]] + num[i] ) 

初始化

dp[0] = 0

遍历顺序

和上题一样,是倒叙遍历

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        #先判断是不是奇数
        if sum(nums) % 2!=0:
            return False
        sum_num = int(sum(nums)/2)
        print(sum_num)
        dp = [0] * (sum_num+1) 
        
        for i in nums:#遍历物品
            for j in range(sum_num,i-1,-1):
                dp[j] = max(dp[j],dp[j-i]+i) #j要大于等于i才有意义,但是不知道为什么j小于i时,没有报错
        if dp[-1] == sum_num:
            return True
        else:
            return False

疑问:j要大于等于i才有意义,但是不知道为什么j小于i时,没有报错。只是结果会有问题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值