给定不同面额的硬币
coins
和一个总金额amount
。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1
。
零钱兑换问题符合最优子结构,比如你想求 amount = 11
时的最少硬币数(原问题),如果你知道凑出 amount = 10
的最少硬币数(子问题),你只需要把子问题的答案加一(再选一枚面值为 1 的硬币)就是原问题的答案。因为硬币的数量是没有限制的,所以子问题之间没有相互制,是互相独立的。
如何列出正确的状态转移方程?
首先要明确我们的目的:用最少的硬币组成目标金额
-
确定 base case
这个很简单,显然目标金额
amount
为 0 时算法返回 0,因为不需要任何硬币就已经凑出目标金额了。 -
确定「状态」,也就是原问题和子问题中会变化的变量。
由于硬币数量无限,硬币的面额也是题目给定的,只有目标金额会不断地向 base case 靠近,所以唯一的「状态」就是目标金额
amount
。 -
确定「选择」,也就是导致「状态」产生变化的行为。
目标金额为什么变化呢,因为你在选择硬币,你每选择一枚硬币,就相当于减少了目标金额。所以说所有硬币的面值,就是你的「选择」。
-
明确
dp
函数/数组的定义。这里直接一步到位,采用自底向上的做法,数组
dp[amount]
代表当目标金额为amount
时需要的最少硬币数(即最终的解)。状态转移方程为:
d p ( n ) = { 0 , n = 0 − 1 , n < 0 m i n { d p ( n − c o i n ) + 1 ∣ c o i n ∈ c o i n s } , n > 0 dp(n)= \begin{cases}0, \quad n = 0 \\-1, \quad n<0 \\min\{ dp(n-coin) + 1 \quad | \quad coin \in coins \},\quad n>0 \end{cases} dp(n)=⎩⎪⎨⎪⎧0,n=0−1,n<0min{dp(n−coin)+1∣coin∈coins},n>0
dp(n)
的定义:输入一个目标金额 n
,返回凑出目标金额 n
的最少硬币数量。
PS:为啥 dp
数组初始化为 amount + 1
呢,因为凑成 amount
金额的硬币数最多只可能等于 amount
(全用 1 元面值的硬币),所以初始化为 amount + 1
就相当于初始化为正无穷,便于后续取最小值。
import java.util.*;
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp, amount+1);
dp[0] = 0;
for(int i = 0; i <= amount; i++){
for(int coin:coins){
if(i-coin >= 0){
dp[i] = Math.min(dp[i-coin]+1, dp[i]);
}
}
}
return (dp[amount] == amount+1) ? -1 : dp[amount];
}
}
时间复杂度:
O(kN)
,k
为硬币面值的种类数空间复杂度:
O(N)