【中等】拼 M 面值硬币问题(动态规划)

一时失言乱红尘 2022-09-10 12:11 283阅读 0赞

题目

现在有 n1 + n2 种面值的硬币,n1 种为普通面值硬币,可以随意取用。n2 种为纪念币,每种最多只能取一个。每种硬币有一个面值,问:凑满 M 面值有多少种取法?

对应 Github 开源项目 class common.Question1

盲题思路:

数据结构:HashMap
方法: 循环 + 条件判断
以 n2 作为突破口,不取 n2 和,取所有可能的 n2
先排序。

开题解法:

数据结构:数组
动态规划 ,两个动规数组 ,不需要排序
dp 含义 dp[i][j] 使用 0-i 种的普通硬币 恰好凑满 j 的取法
对于普通货币数组 arr[i]
动态转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]]
看最后一行
对于纪念币数组 brr[i]
动态转移方程 dp[i][j] = dp[i-1][j] + dp[i-1][j-brr[i]]
看最后一行
最后计算总的凑法 则为两边 dp 钱数互补相乘

解法思路小结

首先从题面来看,需要先进行【可能性推断假设】,在【盲题推断】中,就是因为可能性推断假设没有充分所以有这样不成熟的思路(其实在考虑的时候就觉得不太靠谱)。

动态规划问题,为什么能想到动态规划?
不妨做个假设,或者排除法,取法问题优先考虑是否可以使用动态规划求解。
这个假设先放在这里,在后续的题目中进行验证。

题解

  1. /** * @program: algorithmic-total-solution * @description: 拼 M 面值硬币方法(动态规划问题) * 现在有 n1 + n2 种面值的硬币,n1 种为普通面值硬币,可以随意取用。n2 种为纪念币,每种最多只能取一个。 * 每种硬币有一个面值,问:凑满 M 面值有多少种取法? * @author: wangzibin **/
  2. public class Question1 {
  3. public static void main(String[] args) {
  4. int[] arrCommon = new int[]{ };
  5. int[] arrSpecial = new int[]{ 1,2,5,6,7};
  6. int targetMoney = 30;
  7. long num=getMargeMoneyNum(arrCommon, arrSpecial, targetMoney);
  8. System.out.println("共有 "+num+" 种凑法");
  9. }
  10. private static long getMargeMoneyNum(int[] arrCommon, int[] arrSpecial, int targetMoney) {
  11. int[][] dpCommon = getCoinCommonDp(arrCommon, targetMoney);
  12. int[][] dpSpecial = getCoinSpecialDp(arrSpecial, targetMoney);
  13. // 边界检测
  14. if (dpCommon == null && dpSpecial == null) {
  15. return targetMoney == 0 ? 1 : 0;
  16. } else if (dpCommon == null) {
  17. return dpSpecial[dpSpecial.length - 1][targetMoney];
  18. } else if (dpSpecial == null) {
  19. return dpCommon[dpCommon.length - 1][targetMoney];
  20. }
  21. long num = 0;
  22. for (int i = 0; i <= targetMoney; i++) {
  23. num += (long) dpCommon[arrCommon.length - 1][i] * dpSpecial[arrSpecial.length - 1][targetMoney - i];
  24. }
  25. return num;
  26. }
  27. /** * 普通硬币 dp 表构建 */
  28. public static int[][] getCoinCommonDp(int[] arr, int target) {
  29. if (arr == null || arr.length < 1) {
  30. return null;
  31. }
  32. int[][] dp = new int[arr.length][target + 1];
  33. for (int i = 0; i < arr.length; i++) {
  34. //初始化第一列 凑 0 元
  35. dp[i][0] = 1;
  36. }
  37. for (int j = arr[0]; j < target + 1; j = j + arr[0]) {
  38. //初始化第一行
  39. dp[0][j] = 1;
  40. }
  41. // 动态转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]]
  42. for (int i = 1; i < arr.length; i++) {
  43. for (int j = 1; j < target + 1; j++) {
  44. dp[i][j] = dp[i - 1][j];
  45. int jp = j - arr[i];
  46. dp[i][j] += jp >= 0 ? dp[i][jp] : 0;
  47. }
  48. }
  49. return dp;
  50. }
  51. /** * 纪念币 dp 表构建 */
  52. public static int[][] getCoinSpecialDp(int[] arr, int target) {
  53. if (arr == null || arr.length < 1) {
  54. return null;
  55. }
  56. int[][] dp = new int[arr.length][target + 1];
  57. for (int i = 0; i < arr.length; i++) {
  58. //初始化第一列 凑 0 元
  59. dp[i][0] = 1;
  60. }
  61. // 纪念币只能用一个
  62. if (arr[0] <= target) {
  63. dp[0][arr[0]] = 1;
  64. }
  65. // 动态转移方程 dp[i][j] = dp[i-1][j] + dp[i-1][j-arr[i]]
  66. for (int i = 1; i < arr.length; i++) {
  67. for (int j = 1; j < target + 1; j++) {
  68. dp[i][j] = dp[i - 1][j];
  69. int jp = j - arr[i];
  70. dp[i][j] += jp >= 0 ? dp[i - 1][jp] : 0;
  71. }
  72. }
  73. return dp;
  74. }
  75. }

发表评论

表情:
评论列表 (有 0 条评论,283人围观)

还没有评论,来说两句吧...

相关阅读

    相关 算法-中等-动态规划

    动态规划 理解 记录上一次的状态,之后在现有的数据上进行操作,得到新的状态,从而避免操作过多过长的数据造成难以计算的情况。 举例理解 9x8x6x2=864

    相关 动态规划硬币

    > 题目:几年教师节活动中,公司里为培训讲师提供了不同面值的饮料兑换券(每种面值数量不限),培训讲师可以领取兑换券去食堂兑换鲜榨果汁,要求兑换券和果汁必须等价,姜小虎想要兑换一

    相关 动态规划硬币问题

    凑硬币问题 假设有 1 元,3 元,5 元的硬币若干(无限),现在需要凑出 11 元,问如何组合才能使硬币的数量最少? 用数组d来存储当前每个面值可以对应的合成的最小