思路分析
该问题是一个典型的动态规划问题,也称为“凑零钱问题”。我们需要找出使用最少数量的硬币来凑出给定的总金额 N。这个问题可以通过动态规划(Dynamic Programming, DP)的方法来解决。
-
定义状态:
dp[i]表示凑出金额i所需的最少硬币数量。coinsUsed[i]记录凑出金额i时,最后一个使用的硬币面值。
-
初始化:
dp[0] = 0,凑出金额 0 不需要任何硬币。dp[i] = amount + 1(一个比amount大的数),对于其他金额i,初始化为一个不可能的值,表示当前无法凑出该金额。
-
状态转移:
-
对于每一个金额
i,遍历每一种硬币coin:- 如果
i >= coin,则尝试使用该硬币,并检查是否dp[i - coin] + 1 < dp[i]。 - 如果成立,则更新
dp[i]为dp[i - coin] + 1,并记录coinsUsed[i]为当前硬币coin。
- 如果
-
-
结果构建:
- 从
amount开始,通过coinsUsed数组反向构建出使用的硬币序列。
- 从
-
初始化
dp数组和coinsUsed数组。 -
遍历每一个金额
i,对于每一个硬币coin,如果i >= coin,则更新dp[i]和coinsUsed[i]。 -
遍历完成后,检查
dp[amount],如果仍然为amount + 1,则说明无法凑出该金额,返回空列表。 -
否则,从
amount开始,通过coinsUsed数组反向构建出使用的硬币序列。
代码详解
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
public class Main {
public static List<Integer> solution(int[] coins, int amount) {
int[] dp = new int[amount + 1];
int[] coinsUsed = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i >= coin) {
if (dp[i - coin] + 1 < dp[i]) {
dp[i] = dp[i - coin] + 1;
coinsUsed[i] = coin;
}
}
}
}
if (dp[amount] > amount) {
return new ArrayList<>(); // 返回空列表
}
List<Integer> result = new ArrayList<>();
while (amount > 0) {
result.add(0, coinsUsed[amount]); // 将当前硬币添加到列表前面
amount -= coinsUsed[amount];
}
return result;
}
public static void main(String[] args) {
// 添加测试用例
System.out.println(solution(new int[]{1, 2, 5}, 18).equals(List.of(5, 5, 5, 2, 1)));
System.out.println(solution(new int[]{1, 3, 4}, 6).equals(List.of(3, 3)));
System.out.println(solution(new int[]{5}, 10).equals(List.of(5, 5)));
}
}
知识总结
-
动态规划:
- 动态规划是一种通过将问题分解为更小的子问题来解决复杂问题的方法。
- 在这个问题中,我们使用动态规划来逐步构建出凑出每个金额所需的最少硬币数量。
-
状态转移方程:
- 状态转移方程是动态规划的核心,用于描述如何从子问题的解构建出原问题的解。
- 在这个问题中,状态转移方程是
dp[i] = min(dp[i - coin] + 1),表示凑出金额i所需的最少硬币数量。
理解
动态规划通过记录子问题的解来避免重复计算,从而提高了效率。在这个问题中,我们通过 dp 数组记录凑出每个金额所需的最少硬币数量,并通过 coinsUsed 数组记录凑出每个金额时使用的最后一个硬币,以便最后反向构建出使用的硬币序列。
学习建议
-
理解动态规划的基本概念:
- 动态规划是一种重要的算法思想,建议深入理解其基本概念和原理。
-
多做练习:
- 通过多做动态规划相关的题目,加深对动态规划的理解和掌握。
-
总结归纳:
- 总结归纳动态规划问题的常见类型和解题技巧,形成自己的解题套路。
-
优化代码:
- 在掌握基本解法的基础上,尝试优化代码,提高效率和可读性。