【LeetCode】322.零钱兑换

103 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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 <= 12
  • 1 <= coins[i] <= 2^31 - 1
  • 0 <= 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
}

结语

业精于勤,荒于嬉;行成于思,毁于随。