动态规划 之 排列与组合问题
文章目录
- 518.零钱兑换 II
- 377.组合总和IV
在动态规划算法问题中,存在两种十分相似的问题,一个就是 找零钱问题,另一个是组合总和问题
找零钱问题,其实是零钱问题的一个组合问题,只要零钱的种类和数目相同,就是一种方案;组合总和问题 是一个排列问题,1,2 和 2,1 其实是两种情况的问题
问题的关键点:
- 组合总和计算的是排列,完全背包计算的是组合。比如 1,2 和 2,1 这两个排列,在本题是有区别的,是两种方案;但在完全背包中这两个排列没有区别,只算一种方案。
- 从代码上看,计算排列(本题)需要外层循环枚举体积,内层循环枚举物品;计算组合(完全背包)需要外层循环枚举物品,内层循环枚举体积。
518.零钱兑换 II
518.零钱兑换 II
思路分析:要硬币的种类数目不同,对应的数量不同 才算的是 不同的方案数目,所以动态规划的话,外层循环是遍历硬币的种类,内存循环是amount
递归:因为是相同种类的物品的选择,数目相同的时候,是算同一种方案的,所以要使用两个变量,i用来记录当前遍历到的物品,c用于表示当前所剩余的金额(空间)
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
# 先使用记忆化搜索
@cache
# 前i种物品组成金额c的方案数目
def dfs(i,c):
# 如果遍历完,并且金额为0 就返回1种方案
if i < 0:
return 1 if c == 0 else 0
# 不够的情况,就递归前i-1种
if c < coins[i]:
return dfs(i-1,c)
return dfs(i-1,c) + dfs(i,c-coins[i])
return dfs(len(coins) - 1,amount)
转换为动态规划:
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
n = len(coins)
f = [[0] * (amount + 1) for _ in range(n + 1)]
f[0][0] = 1
for i, x in enumerate(coins):
for c in range(amount + 1):
if c < x:
f[i + 1][c] = f[i][c]
else:
f[i + 1][c] = f[i][c] + f[i + 1][c - x]
return f[n][amount]
当然,这个二维的动态规划,我们可以转化为一维
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
f = [1] + [0] * amount
for x in coins:
for c in range(x, amount + 1):
f[c] += f[c - x]
return f[amount]
377.组合总和IV
377.组合总和IV
思路分析:这个题目就是爬楼梯的变形,爬楼梯的原型是每次可以向上爬一个或者2步,问可以到达顶部n的方案的次数,而在这题的时候,每次向上的步数是nums[i]里面的数,顶部的高度是 target
和零钱问题的对比,由于我们每次的选择的步伐的大小都可以重复的,所以我们并不用传递两个参数,只用传递剩余的空间i,而在每一个空间i下的递归,我们都会逐一枚举可能得步伐
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
@cache
def dfs(i):
# 到达底部返回
if i == 0:
return 1
# return sum(dfs(i-x) for x in nums if x<= i)
ans = []
# 枚举可以移动的步数
for x in nums:
if x <= i:
ans.append(dfs(i-x))
return sum(ans)
return dfs(target)
1:1转化为动态规划
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
# 排列的问题,物品可以重复,物品放内层循环
# def dfs(i):
# # 到达底部返回
# if i == 0:
# return 1
# ans = []
# # 枚举可以移动的步数
# for x in nums:
# if x <= i:
# ans.append(dfs(i-x))
# return sum(ans)
# 转化为动态规划
dp = [1]+[0]*target
for i in range(1,target+1):
ans = 0
for x in nums:
if x<= i:
ans+=dp[i-x]
dp[i] = ans
return dp[target]