完全背包物品数量无限制,可以使用多次的实现方式:背包正序遍历
0-1背包:先物品后背包,物品正序、背包倒序(保证每个物品只用一次)
完全背包:先后顺序都可以,物品正序、背包正序
如果求组合数就是先物品,后背包。
如果求排列数就是先背包,后物品。
518. 零钱兑换 II: 组合数dp += dp[j-x](组合)
中等
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
dp数组定义的大小一般为(背包大小+1)
1、确定dp数组以及下标含义:dp[j]容量为j的背包能装多少种组合的硬币
2、递推公式:组合数--dp += dp[j-x]
3、初始化:dp[0] = 1
4、确定遍历顺序:如果求组合数就是先物品,后背包。如果求排列数就是先背包,后物品。
5、打印dp数组
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount+1)
dp[0] = 1
for i,x in enumerate(coins):
for j in range(x, amount+1):
dp[j] = dp[j] + dp[j-x]
return dp[amount]
377. 组合总和 Ⅳ: 组合数dp += dp[j-x](排列)
中等
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
⚠️题目问的是“组合”,但顺序不同就是不同的组合,本质还是排列 :先物品后背包
此时背包的范围由【x, target+1) 变为【1,target+1),所以在推导公式前要先判断 j >=x
算法思想:抽象为完全背包问题,求容量为target的背包能装多少种排列的硬币
1、确定dp数组以及下标含义:dp[j]容量为j的背包能装多少种排列的元素
2、递推公式:组合数--dp += dp[j-x]
3、初始化:dp[0] = 1
4、确定遍历顺序:如果求组合数就是先物品,后背包。如果求排列数就是先背包,后物品。
5、打印dp数组
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,x in enumerate(nums):
# 防止访问超出范围的索引
if j>=x:
dp[j] += dp[j-x]
return dp[target]
322. 零钱兑换 : 最小物品数 min()
中等
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
算法思想:抽象为完全背包,求容量为amount的背包最少需要多少个硬币能装满。
1、确认dp数组及其下标含义:dp[j] 容量为j的背包最少需要个物品能够装满
2、确定递推公式:dp[j] = min(dp[j], dp[j-x]+1) :凑足总额为j - x的最少个数为dp[j - x],那么只需要加上一个钱币coins[i]即dp[j - x] + 1就是dp[j]
3、初始化:dp[0] = 0,其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖
4、打印
⚠️:其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [inf]*(amount+1)
dp[0] = 0
for i,x in enumerate(coins):
for j in range(x, amount+1):
dp[j] = min(dp[j], dp[j-x]+1)
return dp[amount] if dp[amount] != inf else -1
279. 完全平方数: 最小物品数 min()
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
算法思想:抽象为完全背包,求容量为n的背包最少需要多少个完全平方数能装满。
1、确认dp数组及其下标含义:dp[j] 容量为j的背包最少需要个物品能够装满
2、确定递推公式:dp[j] = min(dp[j], dp[j-x]+1) :凑足总额为j - x的最少个数为dp[j - x],那么只需要加上一个钱币coins[i]即dp[j - x] + 1就是dp[j]
3、初始化:dp[0] = 0,其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖
4、打印
⚠️:其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖
我自己的方法:
class Solution:
def numSquares(self, n: int) -> int:
# 求完全平方数的列表
nums = []
boundary = int(sqrt(n)) # 边界
for i in range(1,boundary+1):
num = i**2 # 求完全平方数
nums.append(num)
# 完全背包:动态规划求最小物品数
dp = [inf]*(n+1) # 其他下表初始为最大数
dp[0] = 0
for i,x in enumerate(nums):
for j in range(x,n+1):
dp[j] = min(dp[j-x]+1,dp[j]) # 最小物品数的推导公式
return dp[n]
优化:
class Solution:
def numSquares(self, n: int) -> int:
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, int(n ** 0.5) + 1): # 遍历物品
for j in range(i * i, n + 1): # 遍历背包
# 更新凑成数字 j 所需的最少完全平方数数量
dp[j] = min(dp[j - i * i] + 1, dp[j])
return dp[n]
139. 单词拆分
中等
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。
拆分时可以重复使用字典中的单词,说明就是一个完全背包!
算法思想:抽象为完全背包背题,求是否能够装满背包
1、确认dp数组及其下标含义:dp[j] 长度为j的字符串,dp[j]为true,表示可以拆分为一个或多个在字典中出现的单词。
2、确定递推公式:如果确定dp[i] 是true,且 [i, j] 这个区间的子串出现在字典里,那么dp[j]一定是true。(i < j )。
if dp[i] and s[i:j] in wordSet: # 检查是否可以分割 dp[j] = True
3、初始化:dp[0] = true
4、打印
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
wordSet = set(wordDict) # 使用集合提高查找效率
dp = [False] * (len(s) + 1)
dp[0] = True # 空字符串可以被分割
for j in range(1, len(s) + 1):
for i in range(j): # i 从 0 到 j-1
if dp[i] and s[i:j] in wordSet: # 检查是否可以分割
dp[j] = True
break # 找到一个分割后可以提前结束
return dp[len(s)]