携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情
题目
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2
输入:coins = [2], amount = 3
输出:-1
示例 3
输入:coins = [1], amount = 0
输出:0
提示
1 <= coins.length <= 121 <= coins[i] <= 2^31 - 10 <= amount <= 10^4
题解
思路
由于每种硬币的数量是无限的,所以这是一个完全背包问题
1:确定dp数组以及下标含义 dp[j]表示凑足总金额为所需的最少硬币数为dp[j]
2:确定递推公式 dp[j]明显可以由dp[j-coins[i]]推出,凑足金额为j-coins[i]的最少硬币数为[j-coins[i]],那么只需要加上一个硬币coins[i]就可以凑足j,也就是dp[j] = dp[j-coins[i]] + 1 所以dp[j]要取所有dp[j-coins[i]] + 1中最小的。 故递推公式为dp[j] = min(dp[j], dp[j-coins[i]] + 1) 不断迭代dp[j],得到最小值
3:dp数组初始化 首先dp[0] = 0 这个很好理解,凑足金额为0的最少硬币数为0,由于是求最小值,所以其他下标对应元素一律初始化为最大整数,这样可以确保遍历中对dp[j]赋值时不会被初始值覆盖掉。
4:确定遍历顺序 由于题目求最少硬币个数,并非求组合数或排列数,所以遍历顺序无所谓,可以先遍历物品,再遍历背包,也可以先遍历背包,再遍历物品。但由于是完全背包,所以遍历背包时,必须是正序。
5:举例推导dp数组 以输入:coins = [1, 2, 5], amount = 5为例 下标 0 1 2 3 4 5 dp[j] 0 1 1 2 2 1
代码
func coinChange(coins []int, amount int) int {
dp := make([]int, amount+1)
dp[0] = 0
for i:=1;i<=amount;i++{
dp[i] = math.MaxInt32
}
for i:=0;i<len(coins);i++{
for j:=coins[i];j<=amount;j++{
if dp[j-coins[i]] != math.MaxInt32{
dp[j] = min(dp[j], dp[j-coins[i]]+1)
}
}
}
// dp[amount] == math.MaxInt32,说明dp[amount]的值还是初始值,并未被更新,此时返回-1
if dp[amount] == math.MaxInt32{
return -1
}
return dp[amount]
}
func min(a, b int) int {
if a < b{
return a
}
return b
}
结语
业精于勤,荒于嬉;行成于思,毁于随。