打卡Day35
- 1.01背包理论基础
- 2.01背包理论基础(滚动数组)
- 3.416. 分割等和子集
1.01背包理论基础
题目链接:01背包理论基础
文档讲解: 代码随想录
01背包:
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。它的暴力解法,每个物品的状态只有两个,取或者不取,可以使用回溯法搜索出所有情况,时间复杂度为
o
(
2
n
)
o(2^n)
o(2n)。
动规五部曲:
(1)确定数组和下标含义
dp[ i ][ j ]表示从下标0~i的物品里任意取,放进容量为 j 的背包,价值总和最大值
(2)确定递推关系式
可以从两个角度进行分析,对于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 ])。
(3)初始化
从递推关系式出发,dp[0][ j ]需要赋值,放入物品 0 时,各种背包容量的价值总和,其中当背包容量小于weight[0]时,不放入物品,价值为0,反之为value[0]。当背包容量为0时,放不进任何物品,则dp[ i ][0] = 0。
(4)遍历顺序
观察递推关系式,dp[ i ][ j ]主要由dp[ i-1 ][ j ]和dp[ i-1 ][ j - weight[i]]求到,只要上方和左上方有值就可以,因此两层循环遍历背包和物品的顺序无论先后,都可以得到答案。
(5)打印数组
M, N = [int(x) for x in input().split()]
space = [int(x) for x in input().split()]
value = [int(x) for x in input().split()]
#任取0~i的物品放入背包容量j的最大价值,i行j列
dp = [[0] * (N + 1) for _ in range(M)]
#初始化
for j in range(space[0], N + 1):
dp[0][j] = value[0]
#遍历
for i in range(1, M):
for j in range(1,N + 1):
if j >= space[i]:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - space[i]] + value[i])
else:
dp[i][j] = dp[i - 1][j]
print(dp[M - 1][N])
2.01背包理论基础(滚动数组)
题目链接:01背包理论基础
文档讲解: 代码随想录
(1)确定数组和下标的含义
dp[ j ]表示背包容量为 j 时物品的最大价值
(2)递推关系式
二维数组的递推关系完全可以将dp[i - 1]那一层拷贝到dp[i]上,因此,递推关系式为dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
(3)初始化
一定要和dp数组的定义吻合,dp[0] = 0,因为递推关系式是取最大值,因此其余位置可以初始化为0
(4)遍历顺序
两层循环,一个 i,一个 j。但 j 是从后往前,因为如果从前往后,物品0会被重复加入多次。二维数组dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!二维数组循环遍历的时候,背包和物品的顺序可以对调,但是在一维数组中,不可以换,因为一旦先遍历背包,背包是倒序遍历的,这样子每个dp[j]就只会放入一个物品。
(5)打印数组
kind, bagspace = [int(x) for x in input().split()]
space = [int(x) for x in input().split()]
value = [int(x) for x in input().split()]
#新建dp数组,初始化为0
dp = [0] * (bagspace + 1)
for i in range(kind):
for j in range(bagspace, space[i] - 1, -1):
dp[j] = max(dp[j], dp[j - space[i]] + value[i])
print(dp[bagspace])
3.416. 分割等和子集
题目链接:416. 分割等和子集
文档讲解: 代码随想录
如果可以分割为两个子集数值和相等,那么就可以抽象为背包问题,背包容量为该数组值总和的一半,问是否存在元素将其装满。这是一个01背包问题,因为其中每个数字只能用一次。同时,需要注意的是,每个元素的重量和价值均为元素的数值。
(1)确定dp数组和下标
dp[ j ]表示容量 j 的背包可以装下的最大重量
(2)递推关系式
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
(3)初始化
从定义来看,dp[0] = 0,因为是取最大值,为了在取值时不被初始值覆盖,因此选择非负的最小值0,来初始化其余下标不为0的元素
(4)遍历顺序
采用一维数组,因此 i 从前往后遍历数组元素, j 从后往前遍历背包容量
(5)打印数组
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
kind = len(nums)
summ = sum(nums)
if summ % 2 == 1:
return False
else:
bagweight = summ / 2
dp = [0] * (bagweight + 1)
for i in range(kind):
for j in range(bagweight, nums[i] - 1, -1):
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
if dp[bagweight] == bagweight:
return True
else:
return False
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
#精简版
if sum(nums) % 2 != 0:
return False
target = sum(nums) / 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)
if dp[target] ==target:
return True
return False