三种背包问题
背包问题主要分为三种:
- 0-1 背包问题:
- 定义:给你一个可装载重量为
W
的背包和 N
个物品,每个物品有重量和价值两个属性。其中第 i
个物品的重量为 wt[i]
,价值为 val[i]
,现在让你用这个背包装物品,最多能装的价值是多少?
- 变种的子集背包问题定义:给一个可装载重量为
sum / 2
的背包和 N
个物品,每个物品的重量为 nums[i]
。现在让你装物品,是否存在一种装法,能够恰好将背包装满?
- 完全背包问题:
- 定义:0-1 背包问题中,每个物品最多可以装一次。完全背包中,所有物品的数量是无限的。
- 因为物品的数量没有限制,因此使用基于贪心策略来做。 循环判断「剩余空间下可容纳的最高性价比物品」,并加入背包。
0-1背包问题
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
1 2 3
| 输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
|
解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution: def canPartition(self, nums: List[int]) -> bool: nums_sum = sum(nums) half_sum = nums_sum // 2 if half_sum * 2 != nums_sum: return False m = len(nums) dp = [[False for _ in range(half_sum + 1)] for _ in range(m + 1)] for i in range(m + 1): dp[i][0] = True for i in range(1, m + 1): for j in range(1, half_sum + 1): if j < nums[i - 1]: dp[i][j] = dp[i - 1][j] else: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i - 1]]) return dp[m][half_sum]
|
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-‘ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-‘ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: if sum(nums) < abs(target) or (sum(nums) + target) % 2 == 1: return 0 return self.subset(nums, (sum(nums) + target) // 2) def subset(self, nums, target): m = len(nums) dp = [[0 for _ in range(target + 1)] for _ in range(m + 1)] dp[0][0] = 1 for i in range(1, m + 1): for j in range(0, target + 1): if j < nums[i - 1]: dp[i][j] = dp[i - 1][j] else: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]] return dp[m][target]
|
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎;
- 如果
x != y
,那么重量为 x
的石头将会完全粉碎,而重量为 y
的石头新重量为 y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
示例 1:
1 2 3 4 5 6 7
| 输入:stones = [2,7,4,1,8,1] 输出:1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
|
解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class Solution: def lastStoneWeightII(self, stones: List[int]) -> int: """ 题目可以抽象为:石头重量之间进行 +、- 符号的组合, 使用最后的结果最小。 记:石头的总重量为 sum、+ 的石头总重量为 pos、 - 的石头总重量为 neg: -> pos = sum - neg -> pos - neg = sum - 2 * neg -> sum - 2 * neg 取最小值时,满足题目要求。 -> 为满足题目要求, neg 需要在不超过 sum/2 的前提下,尽可能的大。 -> 最终题目转化为,在 stones 在 sum/2 最多可以占用的空间 """ m = len(stones) total = sum(stones) n = total // 2 dp = [[False for _ in range(n + 1)] for _ in range(m + 1)] for i in range(m + 1): dp[i][0] = True for i in range(1, m + 1): for j in range(1, n + 1): if j < stones[i - 1]: dp[i][j] = dp[i - 1][j] else: dp[i][j] = dp[i - 1][j] or dp[i - 1][j - stones[i - 1]] ans = None for j in range(n, -1, -1): if dp[m][j]: ans = total - 2 * j break return ans
|
完全背包问题
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
示例 1:
1 2 3 4 5 6 7
| 输入:amount = 5, coins = [1, 2, 5] 输出:4 解释:有四种方式可以凑成总金额: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1
|
解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution: def change(self, amount: int, coins: List[int]) -> int: m = len(coins) dp = [[0 for _ in range(amount + 1)] for _ in range(m + 1)] for i in range(m + 1): dp[i][0] = 1 for i in range(1, m + 1): for j in range(1, amount + 1): if j - coins[i - 1] < 0: dp[i][j] = dp[i - 1][j] else: dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]] return dp[m][amount]
|