0-1背包问题——二维dp解决方案
import sys
input = sys.stdin.read
data = input().split()
index = 0
M = int(data[index])
index += 1
N= int(data[index])
index += 1
weight = []
value = []
for i in range(M):
weight.append(int(data[index+i]))
index += M
for i in range(M):
value.append(int(data[index+i]))
#M * (N+1) ___ 求得dp[M-1][N]
#dp[i][j]代表任选【0-i】的物品放入容量为j的背包里所得的最大价值
#dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
dp = [[0] * (N + 1) for _ in range(M)]
#初始化(都是需要左上已知状态)
for j in range(N+1):
if j >= weight[0]:
dp[0][j] = value[0]
for i in range(1, M):
for j in range(1, N+1):
if j < weight[i]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
print(dp[M-1][N])
0-1背包问题——一维dp解决方案
其实就是复用每一行,细节在注释里
import sys
input = sys.stdin.read
data = input().split()
index = 0
M = int(data[index])
index += 1
N= int(data[index])
index += 1
weight = []
value = []
for i in range(M):
weight.append(int(data[index+i]))
index += M
for i in range(M):
value.append(int(data[index+i]))
#由d[i][j] = max(d[i-1][j], d[i-1][j-weight[i]] + value[i])
#可知,d[i][j],只由上一行,即第i-1行决定
#且只由第i-1行的第j列及之前(左侧)的值决定
#则可以将i这个维度舍去,每次遍历时将第i-1行复用至第i行
dp = [0] * (N+1)
#dp[j] 代表容量为j的背包所能装的最大价值
#dp[j] = dp[j] 当 j < weihgt[i]
#dp[j] = max(dp[j], dp[j-weight[i] + value[i]]) 当 j >= weight[i]
#初始化
#由递推公式可知,只要dp初始化比计算的价值小,就可以成功覆盖
dp[0] = 0
#遍历顺序为行——列,因为我们是要复用每一行
for i in range(M):
#倒序遍历的原因
#由于每个j都需要用到左侧的值
#如果先更新了j,那j之后的值就错乱了(左侧被覆盖)
#且只需要遍历到weight[i],因为j比weight[i]小时,dp[j]不改动
for j in range(N, weight[i]-1, -1):
dp[j] = max(dp[j], dp[j-weight[i]]+ value[i])
print(dp[N])
lc416 分割等和子集问题
——0-1背包
class Solution:
def canPartition(self, nums: List[int]) -> bool:
sum_ = sum(nums)
if sum_ % 2 == 1:
return False
#背包容量为target
#物品价值 == 物品重量
target = sum_ // 2
#求容量为target的背包,能够装的最大价值
#如果最大价值 < target,则背包无法装满
#如果最大价值 == target,则背包可以装满
#由于价值==重量,所以不会出现最大价值 > target的情况
#dp[j]代表容量为j的背包装的最大价值
dp = [0] * (target+1)
dp[0] = 0
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])
if dp[target] == target:
return True
return False
lc1049 最后一块石头的重量II
与lc416分割等和子集基本一样
尽可能分为两堆重量相近的石堆
则以sum // 2为背包容量,石头重量==石头价值,求背包最大价值
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
target = sum(stones) // 2
dp = [0] * (target+1)
for i in range(len(stones)):
for j in range(target, stones[i]-1, -1):
dp[j] = max(dp[j], dp[j-stones[i]] + stones[i])
return sum(stones) - 2 * dp[target]
lc494 目标和
二维方法
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
if (sum(nums) + target) % 2 != 0:
return 0
if sum(nums) < abs(target):
return 0
positives = (sum(nums) + target) // 2
n = len(nums)
#dp[i][j] 代表用【0-i】的物品,填满容量为j的背包共有dp[i][j]种方法
#当j >= nums[i] dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
#当j < nums[i] dp[i][j] = d[i-1][j]
#求dp[n-1][positives] 即用nums装满容量为positives的背包的方法数
dp = [[0] * (positives+1) for _ in range(n)]
#初始化
if positives >= nums[0]:
dp[0][nums[0]] = 1
zero_nums = 0
for i in range(n):
if nums[i] == 0:
zero_nums += 1
dp[i][0] = pow(2, zero_nums)
for i in range(1, n):
for j in range(1, positives+1):
if j < nums[i]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
return dp[n-1][positives]
一维方法
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
if (sum(nums) + target) %2 != 0:
return 0
if sum(nums) < abs(target):
return 0
positives = (sum(nums) + target) // 2
#去掉维度i之后,遍历到i的位置,代表可以使用【0-i】物品
#只不过我们在不断更新、复用dp这一行
#dp[j] 表示填满容量为j的背包有dp[j]种方法
#dp[j] = dp[j] + dp[j-nums[i]]
dp = [0] * (positives + 1)
#初始化
#这里还没开始遍历,所以认为物品0也是不可使用的
#则填满背包容量为0的方法只能是1种(不放任何东西)
dp[0] = 1
for i in range(len(nums)):
for j in range(positives, nums[i] - 1, -1):
dp[j] = dp[j] + dp[j-nums[i]]
return dp[positives]