问题描述
现在有 M 种不同面值的硬币(硬币个数数目无限),如何用这些硬币组合为总数额为 N 的钱,同时满足使用硬币的个数最少?
输入格式
第一行为硬币面值正整数数组 (无序)
第二行为目标总数额,正整数
输出格式
总面值等于目标总数额的,且长度最小的硬币组合数组,结果不要求排序。如果不存在解则返回空数组
解题思路
这是一道经典的动态规划问题,可以把硬币和为dp[i] 表示达到金额 i 所需的最少硬币数。这样对于金额j,对每个硬币面值进行判断,对于特定的硬币面值coin,如果达到j的上一步是取一个coin面值的硬币,那么所需要的硬币数就为dp[i]=dp[i - coin] + 1
- 定义状态:我们可以使用一个数组
dp,其中dp[i]表示达到金额i所需的最少硬币数。 - 初始化:
dp[0] = 0,因为达到金额 0 不需要任何硬币。对于其他金额,初始化为一个较大的值(例如amount + 1),表示初始状态下无法达到这些金额。 - 状态转移:对于每个硬币面值
coin,更新dp数组。如果使用当前硬币coin可以减少达到金额i的硬币数,则更新dp[i]。dp[i] = min(dp[i], dp[i - coin] + 1),其中coin是硬币的面值。 - 记录路径:遍历每个硬币面值,对于每个面值,从面值开始到目标金额N,更新
dp数组。为了记录使用的硬币组合,我们可以使用一个数组usedCoins来记录每个金额i使用的最后一个硬币。 - 构建结果
如果
dp[N]仍然是初始化的较大数,说明没有解决方案。否则,我们可以从dp[N]开始逆向构建硬币组合:
- 从
i = N开始,逆向遍历到0。 - 对于每个
i,如果dp[i]等于dp[i - coin] + 1,则将coin添加到结果数组中,并将i更新为i - coin。
时间复杂度
- 时间复杂度:O(M * N),其中 M 是硬币种类数,N 是目标金额。
- 空间复杂度:O(N),用于存储
dp数组
此题可以衍生出来很多变体
比如完全背包问题:
变体:每种硬币的数量有限,问最少需要多少硬币组成目标金额。
解法:使用完全背包问题的动态规划解法。
思路:
定义一个二维数组 dp[i][j],其中 dp[i][j] 表示使用前 i 种硬币组成金额 j 所需的最少硬币数量。初始化 dp[0][j] = ∞(对于所有 j),因为如果没有硬币,我们无法组成任何金额。
状态转移方程
对于每种硬币 i 和每个金额 j(从0到N),我们更新 dp[i][j]:
dp[i][j]=min(dp[i][j],dp[i−1][j])dp[i][j]=min(dp[i][j],dp[i−1][j]) dp[i][j]=min(dp[i][j],dp[i−1][j−k×coin[i]]+k)
dp[i][j]=min(dp[i][j],dp[i−1][j−k×coin[i]]+k)
其中 k 是当前硬币 i 可以使用的最大数量,且 k \times coin[i] \leq j。