背包动态规划 Backup DP
背包问题
【0-1背包 完全背包】 https://www.bilibili.com/video/BV16Y411v7Y6/?share_source=copy_web&vd_source=5d4accef9045e3ed4e08bbb7a80f3c70
- 递归,自顶向下
- 记忆化搜索
- 递推,自底向上
- 二维数组
- 一维数组
零一背包
有 n 个物品,第 i 个物品的体积为 w[i],价值为 v[i],每个物品至多选一个,求体积和不超过 capacity 时的最大价值和。
- 当前 dfs 维
dfs[i][j] = max(dfs[i-1][j], dfs[i-1][j-w[i]] + v[i]])
- 一维数组递推,从右往左
完全背包
有 n 个物品,第 i 个物品的体积为 w[i],价值为 v[i],每个物品可以重复选,求体积和不超过 capacity 时的最大价值和。
- 一维数组递推,从左往右
常见变形
- 至多装capacity,求方案数/最大价值和
- 恰好装capacity,求方案数/最大/最小价值和
- 至少装capacity,求方案数/最小价值和
01 494. Target Sum 01 背包
递归写法,记忆化搜索
- if nums[i] > c
- dfs(n, c) = dfs(n-1, c)
- else
- dfs(n, c) = dfs(n-1, c) + dfs(n-1, nums[i]-c)
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# p
# s-p
# p-(s-p)=t
# p=(t+s)/2
n = len(nums)
s = sum(nums)
cap = (s+target)/2
@cache
def dfs(i, c):
if i < 0:
return 1 if c == 0 else 0
if nums[i] > c:
return dfs(i-1, c)
return dfs(i-1, c)+ dfs(i-1, c-nums[i])
return dfs(n-1, cap)
记忆化搜索,二维数组
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# p
# s-p
# p-(s-p)=t
# p=(t+s)/2
n = len(nums)
target += sum(nums)
if target < 0 or target % 2:
return 0
cap = target//2
cache = [[-1] * (cap+1) for _ in range(n)]
# @cache
def dfs(i, c):
if i < 0:
return 1 if c == 0 else 0
if cache[i][c] != -1:
return cache[i][c]
res = 0
if nums[i] > c:
res = dfs(i-1, c)
else:
res = dfs(i-1, c)+ dfs(i-1, c-nums[i])
cache[i][c] = res
return res
return dfs(n-1, cap)
递推写法,自底向上的记忆化搜索
f[n] [c] = f[n-1] [c] + f[n-1] [nums[i] - c]
- if nums[i] > c
- f[n+1] [c] = f[n] [c]
- else
- f[n+1] [c] = f[n] [c] + f[n] [nums[i] - c]
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# p
# s-p
# p-(s-p)=t
# p=(t+s)/2
n = len(nums)
target += sum(nums)
if target < 0 or target % 2:
return 0
target = target//2 # target
f = [[0] * (target+1) for _ in range(n+1)]
f[0][0] = 1
for i, x in enumerate(nums):
for c in range(target+1):
# if i == 0:
# if x == c:
# f[i][c] = 1
# else:
if x > c:
f[i+1][c] = f[i][c]
else:
f[i+1][c] = f[i][c] + f[i][c-x]
return f[n][target]
# 空间优化成两行数组,因为每次计算下一个数组都只需要上一个数组的值
f = [[0] * (target+1) for _ in range(2)]
f[0][0] = 1
for i, x in enumerate(nums):
for c in range(target+1):
# if i == 0:
# if x == c:
# f[i][c] = 1
# else:
if x > c:
f[(i+1)%2][c] = f[i%2][c]
else:
f[(i+1)%2][c] = f[i%2][c] + f[i%2][c-x]
return f[n%2][target]
# 优化成一维数组(按照惯例/定律,因为我们从左往右更新,所以一维数组从右往左遍历迭代)
f = [0] * (target+1)
f[0] = 1
for i, x in enumerate(nums):
for c in range(target, -1, -1):
if x > c:
f[c] = f[c]
else:
f[c] = f[c] + f[c-x]
return f[target]
# 继续优化,把x>c的判断用range来取代,因为是原地,所以不用更新
f = [0] * (target+1)
f[0] = 1
for i, x in enumerate(nums):
for c in range(target, x-1, -1):
f[c] = f[c] + f[c-x]
return f[target]