题目

image.png

思路

  • 该题型属于完全背包类型
  • 思路一:暴力递归,想要避免出现重复情况,要遍历每种硬币不同兑换金额下的可能性,也就是每次递归只使用一种硬币,但是它遍历使用它的不同数量,而不是遍历不同硬币下的可能性,第二种方式会导致重复。
  • 思路二:备忘录
  • 思路三:动态规划,定义dp[i][j]表示使用i种硬币可以表示金额j的所有可能性,不管当前硬币是否满足兑换金额都会有dp[i][j] = dp[i-1][j],i中所满足的情况肯定建立在i-1中的基础之上,当当前硬币满足兑换金额再加上dp[i][j - coin]的可能性即可
  • 思路四:优化dp数组的动态规划,画图判断是否可以优化

    代码

    1. //1.暴力递归超时
    2. public int change(int amount, int[] coins) {
    3. if (amount == 0) return 1;
    4. if (amount < 0) return 0;
    5. return recur(amount, coins, new HashMap<>());
    6. }
    7. //记录已使用的key,用于去重处理
    8. Set<String> used = new HashSet<>();
    9. public int recur(int amount, int[] coins ,Map<Integer, Integer> map) {
    10. if (amount == 0) {
    11. StringBuilder key = new StringBuilder();
    12. for (int coin : coins) {
    13. key.append(map.getOrDefault(coin, 0));
    14. }
    15. if (!used.contains(key.toString())) {
    16. used.add(key.toString());
    17. return 1;
    18. }
    19. return 0;
    20. }
    21. if (amount < 0) return 0;
    22. int count = 0;
    23. for (int coin : coins) {
    24. if (amount - coin >= 0) {
    25. map.put(coin, map.getOrDefault(coin, 0) + 1);
    26. count += recur(amount - coin, coins, map);
    27. map.put(coin, map.getOrDefault(coin, 0) - 1);
    28. }
    29. }
    30. return count;
    31. }
    32. //1.暴力递归超时
    33. public int change(int amount, int[] coins) {
    34. if (amount == 0) return 1;
    35. if (amount < 0) return 0;
    36. return recur(amount, coins, 0);
    37. }
    38. public int recur(int amount, int[] coins, int index) {
    39. if (amount == 0) return 1;
    40. if (amount < 0 || index >= coins.length) return 0;
    41. int k = amount / coins[index], count = 0;
    42. for (int i = 0; i <= k; i++) {
    43. count += recur(amount - i * coins[index], coins, index + 1);
    44. }
    45. return count;
    46. }
    47. //2. 备忘录
    48. Map<String, Integer> map = new HashMap<>();
    49. public int change(int amount, int[] coins) {
    50. if (amount == 0) return 1;
    51. if (amount < 0) return 0;
    52. return recur(amount, coins, 0);
    53. }
    54. public int recur(int amount, int[] coins, int index) {
    55. if (amount == 0) return 1;
    56. if (amount < 0 || index >= coins.length) return 0;
    57. String key = coins[index] + "-" + amount;
    58. if (map.containsKey(key)) return map.get(key);
    59. int k = amount / coins[index], count = 0;
    60. for (int i = 0; i <= k; i++) {
    61. count += recur(amount - i * coins[index], coins, index + 1);
    62. }
    63. map.put(key, count);
    64. return count;
    65. }
    66. //3.动态规划
    67. public int change(int amount, int[] coins) {
    68. //dp[i][j]表示使用小于等于i种硬币可以表示j金额的所有可能性
    69. int[][] dp = new int[coins.length + 1][amount + 1];
    70. // 有一种方案凑成 0 元,那就是 一个也不选
    71. dp[0][0] = 1;
    72. for (int i = 1; i <= coins.length; i++) {
    73. for (int j = 0; j <= amount; j++) {
    74. //不管是否使用i种金额能凑出几种可能性,至少可能性是大于等于i-1种金额凑出的可能性
    75. dp[i][j] = dp[i - 1][j];
    76. if (j >= coins[i - 1])
    77. dp[i][j] += dp[i][j - coins[i - 1]];
    78. }
    79. }
    80. return dp[coins.length][amount];
    81. }
    82. //4.优化dp数组的动态规划,由于使用了前一行的dp数组和前一个元素,所以是可以优化的,画图就很快发现
    83. public int change(int amount, int[] coins) {
    84. if (amount == 0) return 1;
    85. if (amount < 0) return 0;
    86. int[] dp = new int[amount + 1];
    87. dp[0] = 1;
    88. for (int coin : coins) {
    89. for (int i = 0; i <= amount; i++) {
    90. if (i - coin >= 0)
    91. dp[i] += dp[i - coin];
    92. }
    93. }
    94. return dp[amount];
    95. }

零钱兑换 II