一、背包问题分类和解法
1.1 背包问题的分类
图来源:代码随想录
1.2 0-1 背包问题的描述
给定一个背包,它的容量为C。现在有一系列的物品i(i从1到N),每个物品都有一个重量w[i]和一个价值v[i]。我们需要在背包中选择一些物品放入,使得总重量不超过背包容量,同时总价值最大化。
注:特点是每个物品要么放入背包(选中),要么不放入背包(不选中),即物品的选择是离散的。而且每个物品只有一件。
1.3 0-1 背包问题的解法
1.3.1 二维dp数组解法
dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为 j 的背包,价值总和最大是多少。
dp = [[0] * (bagWeight + 1) for _ in range(len(weights))]
递推关系: dp[i][j] = max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
初始化:首列全为0,首行根据情况进行赋值
for j in range(weights[0], bagWeight + 1):
dp[0][j] = values[0]
举例递推:递推是从左上方开始的,所以可以调换 for 语句顺序
for i in range(1, len(weights)): # 遍历物品
for j in range(bagWeight + 1): # 遍历背包重量
if j < weights[i]:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])
返回结果:
return dp[len(weights) - 1][bagWeight]
1.3.2 二维dp数组解法的代码
def bagProblem_01(weights, values, bagWeight):
# 二维数组:dp[i][j] 放物品 0——i 在容量为 j 背包种的最大价值和
dp = [[0] * (bagWeight + 1) for _ in range(len(weights))]
# 递推关系:max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
# dp[i][j] = max(dp[i-1][j] , dp[i-1][j-weights[i]]+values[i])
# 初始化:在构建二维数组时就已经对所有位置初始化为 0 ,即已经完成第一列的赋值,还需第一行
for j in range(weights[0], bagWeight + 1):
dp[0][j] = values[0]
# 递推是从左上方开始的,所以可以从左往右按层递推
for i in range(1, len(weights)): # 遍历物品
for j in range(bagWeight + 1): # 遍历背包重量
if j < weights[i]:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i])
return dp[len(weights) - 1][bagWeight]
if __name__ == "__main__":
weight = [2, 2, 3, 1, 5, 2]
value = [2, 3, 1, 5, 4, 3]
bagweight = 1
result = bagProblem_01(weight, value, bagweight)
print(result)
1.3.3 一维数组解法
dp[j]的含义: 容量为 j 的背包所背的最大价值
递推关系: dp[j] = max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
初始化:初始化一个最小且不影响其他元素的元素值
dp[0] = 0
举例递推:一维数组是从二维数组种凝结而来,递推是基于上一层的情况,所以 for 语句不能调换顺序;第二个 for 语句的遍历是倒序,因为正序会叠加多次之前的物品价值
for i in range(1, len(weights)): # 遍历物品
for j in range(bagWeight,weights[i]-1,-1): # 遍历背包重量
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
返回结果:
return dp[bagWeight]
1.3.4 一维dp数组解法的代码
def bagProblem_01(weights, values, bagWeight):
# 一维数组dp[j]:容量为 j 的背包所背的最大价值
dp = [0] * (bagWeight + 1)
# 递推关系:max(不放物品 i 的最大价值和,放物品 i 的最大价值和)
# dp[j] = max(dp[j] , dp[j-weights[i]]+values[i])
# 初始化:初始化一个最小且不影响其他元素的元素值
dp[0] = 0
# 一维数组是从二维数组种凝结而来,递推是基于上一层的情况,所以 for 语句不能调换顺序
# 第二个 for 语句的遍历是倒序,因为正序会叠加多次之前的物品价值
for i in range(1, len(weights)): # 遍历物品
for j in range(bagWeight,weights[i]-1,-1): # 遍历背包重量
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
return dp[bagWeight]
if __name__ == "__main__":
weight = [2, 2, 3, 1, 5, 2]
value = [2, 3, 1, 5, 4, 3]
bagweight = 1
result = bagProblem_01(weight, value, bagweight)
print(result)
二、分割等和子集
2.1 题目
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
2.2 题目链接
2.3 解题思路和过程想法
(1)解题思路
回溯:不断寻找符合要求的集合 path
动态规划:转变为 0-1 背包问题,即找到背包容量为 sum(nums)//2 的集合,物体 i 的重量即为 nums[i]
(2)过程想法
最先想到回溯法,但是提醒超时,转而替换为动态规划
2.4 代码
2.4.1 回溯(提示超时)
class Solution:
def backTracing(self,nums,path,startIndex):
# 递归出口:找到累加和为 sum(nums) // 2 的子集,修改结果值
if sum(path) == sum(nums) // 2:
self.res = True
return
# 剪枝:如果当前累加和超过目标值的一半,直接结束
if sum(path) > sum(nums) // 2:
return
# 递归(回溯)
for i in range(startIndex,len(nums)):
path.append(nums[i])
self.backTracing(nums,path,i+1)
path.pop()
def canPartition(self, nums: List[int]) -> bool:
# 剪枝:如果集合的累加和不是偶数,则直接返回结果
if sum(nums) % 2 != 0:
return False
self.res = False
self.backTracing(nums,[],0)
return self.res
2.4.1 动态规划
class Solution:
def canPartition(self, nums: List[int]) -> bool:
# 找到背包容量为 sum(nums)//2
if sum(nums) % 2 != 0:
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])
return dp[-1] == target