有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。 首先回顾一下0-1背包问题,它和完全背包问题的区别就是是否可以多次取;那么我们再看下0-1背包的状态转移方程:dp[n][j] = max (dp[n-1][j] , dp[n-1][j-v[n]]+v[n]) ,其中,dp[n-1][j] = dp[n-1][j-0v[n]]+0v[n] 。我们很容易发现0-1背包的状态方程建立是考虑取不取的问题,那么完全背包问题就考虑取几个的问题,因此只需要改下0-1背包的状态方程就可以,将0、1改成k,k表示某个物品最多取k次。dp[n][j] = max (dp[n-1][j-countarr[n]]+countarr[n]) ,其中0<=count<=j/arr[n]
1、 本题为经典的完全背包问题,但是下列传统思路的代码会超时,需要进一步改进!实际上,复杂度的提升是决策层循环带来的,因此将决策层优化就能提升效率。
class Solution:
def back_pack_i_i_i(self, a , v , m: int) -> int:
# write your code here
# 动规思路:dp[n][j] 表示前n个物品取不超过j的体积的最大价值
# dp[n][j] = max (dp[n-1][j-count*arr[n]]+count*arr[n]) ,其中0<=count<=j/arr[n]
dp = [[-1] * (m + 1) for _ in range(len(a))]
for j in range(m+1):
count = j // a[0]
for c in range(count, -1, -1): #决策层循环
if c * a[0] <= j:
dp[0][j] = c * v[0]
break
else:
dp[0][j] = 0
def func(n, j):
if n == 0:
return dp[n][j]
res = 0
count = j // a[n]
for c in range(count + 1): #决策层循环
if dp[n - 1][j - c * a[n]] == -1:
dp[n - 1][j - c * a[n]] = func(n - 1, j - c * a[n])
res = max(res, dp[n - 1][j - c * a[n]] + c * v[n])
return res
return func(len(a) - 1, m)
其实决策层那里经过数学推导可以得到:dp[n][j]=max(dp[n-1][j],dp[n][j-v[n]]+v[n]); 这与0-1背包非常相似,我们看下0-1背包的方程:dp[n][j] = max (dp[n-1][j] , dp[n-1][j-v[n]]+v[n])。其实区别就是在后面取最后一个物品的时候算前n个而不是n-1个。01背包考虑是放或者不放,所以考虑前n-1个物品的最大值与最后一个物品取不取的关系,而完全背包是不放或者放多件的关系,考虑的是前n件物品的最大值与还能不能放的问题。反正记住一点就行,完全背包的max中第二项考虑前n个,其他与0-1背包一样,代码如下:
class Solution:
import sys # 导入sys模块
sys.setrecursionlimit(100000) # 将默认的递归深度修改为100000
def back_pack_i_i_i(self, a , v , m: int) -> int:
# write your code here
# 动规思路:dp[n][j] 表示前n个物品取不超过j的体积的最大价值
# dp[n][j] = max (dp[n-1][j] , dp[n][j-v[n]]+v[n])
if(len(a)==0):
return 0
dp = [[-1] * (m + 1) for _ in range(len(a))]
for j in range(m+1):
count = j // a[0]
for c in range(count, -1, -1): #优化决策层循环
if c * a[0] <= j:
dp[0][j] = c * v[0]
break
else:
dp[0][j] = 0
def func(n, j):
if n == 0:
return dp[n][j]
if j==0:
return 0
if dp[n-1][j]==-1:
dp[n - 1][j] = func(n-1,j)
res = dp[n - 1][j]
if j>=a[n]:
if dp[n][j-a[n]]==-1:
dp[n][j - a[n]] = func(n,j-a[n])
res = max(res,dp[n][j - a[n]]+v[n])
dp[n][j] = res
return res
return func(len(a) - 1, m)
该题是经典的求方案数,我们知道动态规划善于用来解决 求最值、求方案数、求可行性三种题目,那么本题就是求方案数。但是本题要看清楚是只能取一次还是能够重复取,如果是重复取的话就是完全背包问题。先用0-1背包构建状态方程,然后再改n-1为n。请参考下面代码:
class Solution:
def change(self, amount: int, coins ) -> int:
# 动规思路:dp[n][j]表示前n个数凑成j的方案数
# dp[n][j] = max(dp[n-1][j],dp[n-1][j-a[n]]) 0-1背包问题
# dp[n][j] = max(dp[n-1][j],dp[n][j-a[n]]) 完全背包问题
if amount==0:
return 1
dp = [[-1] * (amount + 1) for _ in range(len(coins) + 1)]
# 老是初始化出错,记得完全背包初始化时要考虑多个
for j in range(amount+1):
if j >= coins[0] and j // coins[0] == j / coins[0]:
dp[0][j] = 1
else:
dp[0][j] = 0
def func(n, j):
if n == 0:
return dp[n][j]
if j == 0:
return 1
if dp[n - 1][j] == -1:
dp[n - 1][j] = func(n - 1, j)
res = dp[n - 1][j]
if j >= coins[n]:
if dp[n][j - coins[n]] == -1:
dp[n][j - coins[n]] = func(n, j - coins[n])
res = dp[n - 1][j] + dp[n][j - coins[n]]
dp[n][j] = res
return res
return func(len(coins) - 1, amount)
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
#完全背包问题 dp[i][j] 前i个数凑成j所需的最少银币数
#dp[i][j] = min(dp[i-1][j],dp[i][j-coins[i]])
if amount==0:
return 0
dp = [[99999999]*(amount+1) for _ in range(len(coins)+1)]
for j in range(amount+1):
if j//coins[0]==j/coins[0]:
dp[0][j] = j//coins[0]
# else:
# dp[0][j] = 0 # 注意 不能赋值为0 直接不用赋值就行
for i in range(1,len(coins)):
for j in range(0,amount+1): # 注意遍历开始条件
if j>=coins[i]:
dp[i][j] = min(dp[i-1][j],dp[i][j-coins[i]]+1)
else:
dp[i][j] = dp[i-1][j]
res = dp[len(coins)-1][amount]
return -1 if res==99999999 else res
注意此题和上一题的区别,上一题是完全背包的典型例题,不考虑排列方式,本题比上一题难在排列方式,如果不考虑排列代码如上面零钱兑换那题:凡是考虑装满背包的方案数的问题都是考虑 dp[i] += dp[i-num[j]] 的所有方案,dp[i] 表示总和为i的所有排列数。
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
#动规思路:前缀完全背包动态问题 dp[i][j]表示前i个数为j的组合个数
#方程建立: dp[i][j] = max(dp[i-1][j],dp[i][j-nums[i]])
n = len(nums)
dp = [[-1]*(target+1) for _ in range(n+1)]
#初始化
for j in range(target+1):
if nums[0]==j:
dp[0][j] = 1
else:
dp[0][j] = 0
for i in range(n):
dp[i][0] = 0
#递归方程
def func(i,j):
if i==0 or j==0:
return dp[i][j]
if dp[i-1][j]==-1:
dp[i-1][j]=func(i-1,j)
res = dp[i-1][j]
if j>=nums[i]:
if dp[i][j-nums[i]]==-1:
dp[i][j-nums[i]]=func(i,j-nums[i])
# res = dp[i-1][j] + dp[i][j-nums[i]] #这样写就是不考虑排列方式
res = dp[i-1][j] + 复杂的排列组合公式
dp[i][j] = res
return res
res = func(n-1,target)
return res
考虑组合方式的代码如下:
class Solution:
def numSquares(self, n: int) -> int:
#转化为完全背包问题,物品就是完全平方数
nums = []
for i in range(1,n+1):
if(i*i<=n):
nums.append(i*i)
else:
break
m = len(nums)
dp = [[999999]*(n+1) for _ in range(m+1) ]
for j in range(n+1):
if (j//nums[0]==j/nums[0]):
dp[0][j] = j//nums[0]
for i in range(1,m):
for j in range(n+1):
if j>=nums[i]:
dp[i][j] = min(dp[i-1][j],dp[i][j-nums[i]]+1)
else:
dp[i][j] = dp[i-1][j]
return dp[m-1][n]