难度:
中等
描述:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例:
1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
2:
输入: coins = [2], amount = 3
输出: -1
说明: 你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
语言:
swift
解析:
这是一道比较基础的关于动态规划的题。那我们就按着动态规划的标准解题步骤来一步一步解答。
我们需要拆分子问题,找到状态转移方程。
首先拆分子问题:
1. 如何用最少的 coins 凑够 0 元?
2. 如何用最少的 coins 凑够 1 元?
3. 如何用最少的 coins 凑够 2 元?
4. ...
5. 如何用最少的 coins 凑够 n 元?
我们分别解决这些子问题,我们用d(i) = j
来表示,凑够i
元最少需要j
个硬币:
1. 如何用最少的 coins 凑够 0 元?
对于该情况,显然是需要0枚硬币就能凑够0元,故:
d(0) = 0
2. 如何用最少的 coins 凑够 1 元?
为了凑够1元,我们需要1枚1元的硬币,同时我们要和上一个问题想一想有没有什么关联?为了解决子问题2,就等于先解决了子问题1,在解决了子问题1的基础解决了子问题2。
d(1) = d(1 - 1) + 1 = d(0) + 1 = 0 + 1 = 1
// d(1) 代表为了凑够 1 元
// d(1 - 1) + 1 代表为了解决这个问题,我们需要解决凑够 0 元情况下的结果,再加上凑够 1 元需要的一块的这一种结果
3. 如何用最少的 coins 凑够 2 元?
为了求出最少凑够2元,我们同样可以得出:解决问题2,凑够1元的情况下,再凑够1元:
d(2) = d(2 - 1) + 1 = d(1) + 1 = 1 + 1 = 2
// d(2) 代表为了凑够 2 元
// d (2 - 1) + 1 代表为了解决这个问题,我们需要解决凑够 1 元的情况下,再加上凑够剩下的 1 元
但是题目上,我们不仅有1元的硬币,还有2元的硬币,这个时候,我们仅需要一枚2元的硬币就可以凑够2元,也就是:凑够 2 元 = 凑够 0 元 + 凑够 2 元
d(2) = d(2 - 2) + 1 = d (0) + 1 = 0 + 1 = 1
// d(2) 代表为了凑够 2 元
// d(2 - 2) + 1 代表可以将凑够二元分解为,先凑够 0 元,再用 1 个 2 元的硬币来凑够 2 元
其实在这个时候,d(2) = 1
,可以将上面的两个方程简化一下为:
d(2) = min{d(2 - 1) + 1, d(2 - 2) + 1}
那么我们就可以抽象成以下状态转移方程:
d(n) = min{d(n - c) + 1}
意思为,为了凑够 n 元,我们需要找到不同的面值 c 下的最优解。
所以为了凑够 11 元,本题来说,我们需要如下状态方程:
d(11) = min{d(11 - 1) + 1, d(11 - 2) + 1, d(11 - 5) + 1}
= min{d(10) + 1, d(9) + 1, d(6) + 1}
d(10) = ...
d(9) = ...
d(6) = ...
...
编程的话,也就是需要将状态转移方程表达出来。我们需要一个dict
来存储凑够不同价格时候的最优解。可以循环的条件为amount - coin >= 0
,举例为如果硬币只有10元面值,而需要凑够5元,这时候5 - 10 < 0
,不满足条件。
代码如下:
class Solution {
func coinChange(_ coins: [Int], _ amount: Int) -> Int {
if amount == 0 {
return 0
}
var dict = [Int : Int]()
dict[0] = 0
for index in 1...amount {
var minLength = -1
for coinsIndex in 0...coins.count - 1 {
let previousValue = index - coins[coinsIndex]
if previousValue >= 0 {
if (dict[previousValue] != nil) && dict[previousValue]! != -1 {
if minLength == -1 {
minLength = dict[previousValue]! + 1
} else {
minLength = min(minLength, dict[previousValue]! + 1)
}
}
}
}
dict[index] = minLength
}
return dict[amount]!
}
}
总结
动态规划很有趣,需要好好研究一下。