问题描述
小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。
例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。
测试样例
样例1:
输入:
coins = [1, 2, 5], amount = 18
输出:[5, 5, 5, 2, 1]
样例2:
输入:
coins = [1, 3, 4], amount = 6
输出:[3, 3]
样例3:
输入:
coins = [5], amount = 10
输出:[5, 5]
要解决这个问题,我们可以使用动态规划(Dynamic Programming)的方法。动态规划是一种通过将问题分解为子问题并存储子问题的解来解决复杂问题的方法。
解题思路
-
问题理解:
- 我们需要找到一种组合,使得使用最少数量的硬币凑出给定的总金额
amount。 - 硬币的面值和数量是无限的。
- 我们需要找到一种组合,使得使用最少数量的硬币凑出给定的总金额
-
数据结构选择:
- 我们可以使用一个数组
dp,其中dp[i]表示凑出金额i所需的最少硬币数量。 dp[0]初始化为0,因为凑出0金额不需要任何硬币。- 其他位置
dp[i]初始化为一个较大的值(例如amount + 1),表示初始状态下无法凑出该金额。
- 我们可以使用一个数组
-
算法步骤:
- 遍历每个金额
i从1到amount。 - 对于每个金额
i,遍历每种硬币coin,如果coin小于或等于i,则更新dp[i]为dp[i - coin] + 1和当前dp[i]中的较小值。 - 最终,
dp[amount]就是凑出amount所需的最少硬币数量。
- 遍历每个金额
-
记录硬币组合:
- 为了记录使用的硬币组合,我们可以在更新
dp[i]时,同时记录使用的硬币面值。
- 为了记录使用的硬币组合,我们可以在更新
import java.util.ArrayList;
import java.util.List;
public class Main {
public static List<Integer> solution(int[] coins, int amount) {
// Edit your code here
List<List<Integer>> dp = new ArrayList<>();
for (int i = 0; i <= amount; i++) {
dp.add(new ArrayList<>());
}
for (int i = 1; i < dp.size(); i++) {
int index = 0;
int minSize = Integer.MAX_VALUE;
int count = 0;
for (int j = 0; j < coins.length; j++) {
int temp = i - coins[j];
if(temp >= 0){
List<Integer> list = dp.get(temp);
if(minSize > list.size()){
minSize = list.size();
index = temp;
count = coins[j];
}
}
}
List<Integer> list = dp.get(index);
dp.get(i).addAll(list);
dp.get(i).add(count);
}
return dp.get(amount);
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(new int[]{1, 2, 5}, 18).equals(List.of(5, 5, 5, 2, 1)));
}
}
总结
通过动态规划的方法,我们可以有效地找到凑出给定金额所需的最少硬币数量,并且可以记录使用的硬币组合。这个方法的时间复杂度是 O(amount * n),其中 n 是硬币的种类数。