完全背包 卡码网第52题:
一维dp:
import sys
def Knapsack(n, v, weights, values):
dp = [0] * (v + 1)
for i in range(n):
for j in range(weights[i], v + 1):
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
return dp[v]
if __name__== '__main__':
weights, values = [],[]
n, v = map(int, input().strip().split())
for line in sys.stdin:
weight, value = map(int, line.strip().split())
weights.append(weight)
values.append(value)
print(Knapsack(n, v, weights, values))
二维dp:
import sys
def Knapsack(n, v, weights, values):
dp = [[0] * (v + 1) for _ in range(n)]
for i in range(n):
for j in range(1, v + 1):
if j < weights[i]:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - weights[i]] + values[i])
print(dp)
return dp[n - 1][v]
if __name__== '__main__':
weights, values = [],[]
n, v = map(int, input().strip().split())
for line in sys.stdin:
weight, value = map(int, line.strip().split())
weights.append(weight)
values.append(value)
print(Knapsack(n, v, weights, values))
518. 零钱兑换 II
感觉和目标和很像吼。
一维dp:
空集可以组成总额为0的金额,初始化dp[0] = 1。可以重复使用,因此正向更新一维dp数组可以重复使用。
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount + 1)
dp[0] = 1
for coin in coins:
for j in range(coin, amount + 1):
dp[j] += dp[j - coin]
return dp[amount]
二维dp:
加深理解,再写一版二维dp实现。
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [[0] * (amount + 1) for _ in range(len(coins) + 1)]
dp[0][0] = 1
for i in range(1, len(coins) + 1):
for j in range(amount + 1):
dp[i][j] = dp[i - 1][j]
if j >= coins[i - 1]:
dp[i][j] += dp[i][j - coins[i - 1]]
return dp[len(coins)][amount]
377. 组合总和 Ⅳ
多重背包(动态规划)实现:
这道题二维dp甚至不能有效的解决问题??尝试了很久至少我设计的状态和转移方程没法合理的进行状态填充。
而一维dp可以非常优雅地解决,目标总和为j,将总和拆解成j - nums[i]分别求排列数再加起来,就是和为j的排列总数。内循环遍历nums[i]就是遍历一个完备事件组来得到和为j(这里体现条件nums[i]互不相同这个条件的重要性,只有nums[i]互不相同,每次使用的nums[i]才独立且唯一)将他们加起来就是和为j的所有排列数。
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
dp = [0] * (target + 1)
dp[0] = 1
for j in range(1, target + 1):
for i in range(len(nums)):
if j - nums[i] >= 0:
dp[j] += dp[j - nums[i]]
return dp[target]
带备忘的递归实现:
实际上带备忘的递归实现比较直观:
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
memo = {}
def dfs(remaining):
if remaining == 0:
return 1
if remaining in memo:
return memo[remaining]
count = 0
for num in nums:
if remaining - num >= 0:
count += dfs(remaining - num)
memo[remaining] = count
return count
return dfs(target)
今日总结:
主要纠结的点在第三题,总觉得总和为j - nums[i]有dp[j - nums[i]]种排列方法,加入nums[i]方法数取决于把nums[i]插在什么位置,这样想其实有点跟动态规划的思想背道而驰了。动态规划并不考虑这个问题,只关心达到总和j的排列数量,并且外循环的过程中已经隐式的包含了这个问题,对于任何一个num,考虑外面的大循环,如果每次都加在末尾,其实已经加在所有可能的位置了。