题目
一、解题思路
动态规划:一般用于求最值,核心在于找 状态转移方程式
动态规划问题一定具备 最优子结构,通过求解子结构的最优解 从而获得原问题的最优解
例子: conis=[1,2,5] amount=7
1.原问题:凑金额7 所需要的最少硬币数是多少?
1.1 子问题:凑金额6 所需要的最少硬币数是多少?
1.1.1 子问题 凑金额5 所需要的最少硬币数是多少?
…
1.1.1… 子问题 凑金额1 所需要的最少硬币数是多少?
我们需要做的就是 求出每个子问题的答案 。
求每个子问题的这个过程,其实就是 状态转移方程式 因为过程一样,所以就可以用这个方程式来表示
2.初始化一个dp数组用于存放子问题的答案
dp数组除第一个元素外,其余所有元素均为正无穷。因金额为0时,所需硬币数为0,所以将dp[0]=0
以下为求解子问题的过程:
金额为1时:1+dp[0]=1 1:代表1个1元面值的硬币 dp[0]:凑金额0 需要的最少硬币数0 1:凑金额1需要的硬币数为1 min(1+dp[0],dp[1])—> 1 dp[1]最开始为正无穷
答案:1
金额为2时:1+dp[1]=2 1:代表1个1元面值的硬币 dp[1]:凑金额1 需要的最少硬币数1 2:凑金额2需要的硬币数为2 min(1+dp[1],dp[2])—> dp[2]=2
2+dp[0]=1 1:代表1个2元面值的硬币 dp[0]:凑金额0 需要的最少硬币数0 1:凑金额2需要的硬币数为1 min(1+dp[0],dp[2])—>min(1,2)—> dp[2]=1
答案:1
以上总结如下:
dp[2]=min(1+dp[2-1],dp[2]) —>dp[2]=2 2-1:表示金额2减去coins中遍历的面值金额1 【差值要大于等于0,才能凑】
dp[2]=min(1+dp[2-2],dp[2]) —>dp[2]=1 2-2:表示金额2减去coins中遍历的面值金额2
因此状态转移方程式为: dp[n]=min(1+dp[n-coins[i]],dp[n])
二、代码
代码如下:
def coinChange(self, coins: List[int], amount: int) -> int:
res=float('INF') # 代表正无穷
dp=[res]*(amount+1) #dp数组的定义:当目标金额为i时,至少需要dp[i]枚凑出
dp[0]=0 # 金额为0时,结果则为0
# amount=7 从金额1开始,凑金额1 2 3 4 5 6 7 ,每种金额需要的最少硬币数
for i in range(1,amount+1):
# 遍历已有的金额面值 coins=[1,2,5]
for coin in coins:
diff=i-coin #只有金额数大于等于已有的面值数 才能进行凑数
if diff>=0:
# 这里涉及到 状态转移方程式 金额数=某金额面值的张数y+dp[x]
# 初dp[0]=0,其余dp[x]初已经始化为正无穷 但在求每种金额数需要的最少硬币数时,会将其最优值保存到相应的dp数组里
dp[i]=min(1+dp[diff],dp[i])
if dp[amount]!=inf: # 如果dp数组的最后一个数据不为inf,所以此时保存的数据已经改变,是凑出该金额需要的最少数量
return dp[amount]
else:
return -1