给定一个target(可以是数字或字符串)
给定数组nums(nums的元素可以是数字或字符)
问:能否使用nums中的元素做各种排列组合得到target
1.组合问题
377.组合总和 Ⅳ
494.目标和
518.零钱兑换 II
状态转移
dp[i] += dp[i-num]
2.True、False问题:
139.单词拆分
416.分割等和子集
状态转移
dp[i] = dp[i] or dp[i-num]
3.最大最小问题:
474.一和零
322.零钱兑换
状态转移
dp[i] = min(dp[i],dp[i-num]+1) 或 max(dp[i],dp[i-num]+1)
1.如果是0-1背包,即数组中的元素不可重复使用,nums放在外循环,target在内循环,且内循环倒序;
for num in nums:
for i in range(target, num-1, -1):
2.如果是完全背包,即数组中的元素可重复使用,nums放在外循环,target在内循环。且内循环正序。
for num in nums:
for i in range(num, target+1):
完全背包内外循环可以调换
3.如果组合问题需考虑元素之间的顺序,需将target放在外循环,将nums放在内循环。
for i in range(1, target+1):
for num in nums:
参考资料:
https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/
377. 组合总和 Ⅳ
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:
nums = [1, 2, 3]
target = 4
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
因此输出为 7。
494. 目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
方法一:
求方法数:dp数组记录方法数
定义状态:dp[i][j]表示nums中下标为0-i的元素,加减得到j的方法数
状态转移方程:d[i][j] = d[i-1][j-nums[i] + d[i-1][j+nums[i]
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
Sum = sum(nums)
if S>Sum or S<-Sum:
return 0
n = len(nums)
dp = [[0 for _ in range(Sum*2+1)] for _ in range(n)]
if nums[0]==0:
dp[0][Sum] = 2
else:
dp[0][Sum-nums[0]] = 1
dp[0][Sum+nums[0]] = 1
for i in range(1,n):
for j in range(Sum*2+1):
if j+nums[i]<Sum*2+1:
dp[i][j] += dp[i-1][j+nums[i]]
if j-nums[i]>=0:
dp[i][j] += dp[i-1][j-nums[i]]
return dp[-1][Sum+S]
方法二:
0-1背包问题
原问题等同于:找到nums中的两个子集,使得其差为target
正子集P,负子集N
SP - SN = target
SP + SN + SP + SN = target + SP +SN
2*SP = target + Sum
等同于找,和为(target+Sum)//2的子集
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
Sum = sum(nums)
if S>Sum or S<-Sum or (Sum+S)%2!=0:
return 0
n = len(nums)
SP = (Sum+S)//2
dp = [0 for _ in range(SP+1)]
dp[0] = 1
for num in nums:
for j in range(SP,num-1,-1):
dp[j] += dp[j-num]
return dp[-1]
518. 零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
与377的区别,元素顺序不同视为同一组合方式
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0 for _ in range(amount+1)]
dp[0] = 1
for coin in coins:
for i in range(1,amount+1):
if i>=coin:
dp[i] += dp[i-coin]
return dp[amount]
139.单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
n = len(s)
dp = [False for _ in range(n+1)]
dp[0] = True
for i in range(n):
for j in range(i+1,n+1):
if dp[i] and s[i:j] in wordDict:
dp[j] = True
return dp[-1]
416.分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
0-1背包
class Solution:
def canPartition(self, nums: List[int]) -> bool:
Sum = sum(nums)
if Sum%2!=0:
return False
target = Sum//2
dp = [None for _ in range(target+1)]
dp[0] = True
for num in nums:
for i in range(target,num-1,-1):
dp[i] = dp[i] or dp[i-num]
return dp[-1]
474.一和零
在计算机界中,我们总是追求用有限的资源获取最大的收益。
现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。
你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。
注意:
给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
m个0,n个1可以看作是背包的容量
数组中的字符串看作是放入背包的物品
dp[i][j][k] 表示在数组[0:i+1]个字符串中,j个0,k个1可以拼出的最大数量
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
if not strs:
return 0
dics = []
for s in strs:
dic = [0,0]
for c in s:
if c=='0':
dic[0] += 1
elif c=='1':
dic[1] += 1
dics.append(dic)
l = len(strs)
f = [[0 for _ in range(n+1)] for _ in range(m+1)]
for j in range(m+1):
for k in range(n+1):
if j>=dics[0][0] and k>=dics[0][1]:
f[j][k] = 1
for i in range(1,l):
for j in range(m,dics[i][0]-1,-1):
for k in range(n,dics[i][1]-1,-1):
f[j][k] = max(f[j][k],f[j-dics[i][0]][k-dics[i][1]]+1)
return f[-1][-1]
322.零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
说明:
你可以认为每种硬币的数量是无限的。
硬币可以重复使用,完全背包
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if not coins:
return -1
dp = [9999 for _ in range(amount+1)]
dp[0] = 0
for coin in coins:
for i in range(coin,amount+1):
dp[i] = min(dp[i],dp[i-coin]+1)
if dp[amount]==9999:
return -1
return dp[-1]
参考资料:
https://leetcode-cn.com/problems/target-sum/solution/bei-bao-da-qia-ti-20-1bei-bao-qia-hao-zhuang-man-b/