leetcode 518. Coin Change 2 | 518. 零钱兑换 II(暴力递归->傻缓存->动态规划)

秒速五厘米 2022-09-12 03:50 337阅读 0赞

题目

https://leetcode.com/problems/coin-change-2/
在这里插入图片描述

题解

本题和 leetcode 377. Combination Sum IV | 377. 组合总和 Ⅳ(动态规划) 有点像,只不过 377 是不考虑顺序的(排列),本题是考虑顺序的(组合)

而本题与 leetcode 39. Combination Sum | 39. 组合总和(回溯) 的区别是,39 题仅有有限个 coin 可用,而本题的 coins 是没有数量限制的。

1、暴力递归(TLE)

先用简单的“尝试”的方法,即递归的方法。
递归函数返回当目标是target时,返回有多少种组合方式。

  1. class Solution {
  2. public int change(int amount, int[] coins) {
  3. return process(coins, 0, amount);
  4. }
  5. // 当目标是target时,返回有多少种组合方式。
  6. // i表示当前可用的硬币位置,大于等于位置i的硬币可用,避免1+2与2+1重复。
  7. public int process(int[] coins, int i, int target) {
  8. if (target == 0) return 1;
  9. if (target < 0) return 0;
  10. int count = 0;
  11. for (int j = i; j < coins.length; j++) {
  12. count += process(coins, j, target - coins[j]);
  13. }
  14. return count;
  15. }
  16. }

在这里插入图片描述

2、傻缓存,记忆化搜索(AC)

给递归函数加个傻缓存,避免重复计算。

(递归入口)向 (basecase)的 带有傻缓存的递归,叫记忆化搜索。
(basecase) 向 (递归入口)填写 结构化表格,叫动态规划。动态规划的目标,即最终要返回的坐标,就是递归入口的参数对应的坐标。

  1. class Solution {
  2. int N;
  3. public int change(int amount, int[] coins) {
  4. N = coins.length;
  5. int[][] dp = new int[N][amount + 1];
  6. for (int[] d : dp) {
  7. Arrays.fill(d, -1);
  8. }
  9. return process(coins, 0, amount, dp);
  10. }
  11. // 当目标是target时,返回有多少种组合方式。
  12. // i表示当前可用的硬币位置,大于等于位置i的硬币可用,避免1+2与2+1重复。
  13. public int process(int[] coins, int i, int target, int[][] dp) {
  14. if (target == 0) return 1;
  15. if (target < 0) return 0;
  16. if (dp[i][target] != -1) return dp[i][target];
  17. int count = 0;
  18. for (int j = i; j < N; j++) {
  19. count += process(coins, j, target - coins[j], dp);
  20. }
  21. dp[i][target] = count;
  22. return count;
  23. }
  24. }

在这里插入图片描述

3、DP(AC)

将带有傻缓存的递归,转化成自底向上的 dp。

方法是,先填写 basecase,然后根据 递归函数调用时的参数,确定依赖关系。然后,根据依赖关系的顺序,填写 dp 表。

把带有傻缓存的递归,翻译成 dp 的过程:
在这里插入图片描述
依赖关系决定填写顺序:
在这里插入图片描述
需要注意的是,dp 表是按顺序全部被填满的,有一些没有必要计算的位置也将被计算,而记忆化搜索只计算那些需要依赖的位置。因此,dp 的效率可能会比记忆化搜索更差一些。

  1. class Solution {
  2. int N;
  3. public int change(int amount, int[] coins) {
  4. N = coins.length;
  5. int[][] dp = new int[N][amount + 1];
  6. for (int[] d : dp) {
  7. Arrays.fill(d, -1);
  8. }
  9. // base case: 第0列
  10. for (int i = 0; i < N; i++) {
  11. dp[i][0] = 1;
  12. }
  13. // base case: 第N-1行
  14. for (int i = 1; i <= amount; i++) {
  15. if (i % coins[N - 1] == 0) dp[N - 1][i] = 1;
  16. else dp[N - 1][i] = 0;
  17. }
  18. // 根据递归参数的依赖关系,填dp表
  19. for (int target = 1; target <= amount; target++) {
  20. for (int i = N - 2; i >= 0; i--) {
  21. int count = 0;
  22. for (int j = i; j < N; j++) {
  23. if (target - coins[j] >= 0) count += dp[j][target - coins[j]];
  24. }
  25. dp[i][target] = count;
  26. }
  27. }
  28. return dp[0][amount];
  29. }
  30. }

在这里插入图片描述

发表评论

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

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

相关阅读

    相关 518. 零钱兑换 II

    > 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 > > 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总