From c732207ff87166aa76837502bcdeb5d3301c71b5 Mon Sep 17 00:00:00 2001 From: markilue <745518019@qq.com> Date: Mon, 5 Dec 2022 13:38:01 +0800 Subject: [PATCH] =?UTF-8?q?leecode=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../markilue/leecode/dynamic/T12_Change.java | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T12_Change.java diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T12_Change.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T12_Change.java new file mode 100644 index 0000000..4f27e45 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T12_Change.java @@ -0,0 +1,152 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: markilue + * @CreateTime: 2022-12-05 10:50 + * @Description: TODO 力扣518题 零钱兑换II: + * 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 + * 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 + * 假设每一种面额的硬币有无限个。 + * 题目数据保证结果符合 32 位带符号整数。 + * @Version: 1.0 + */ +public class T12_Change { + + @Test + public void test() { + int amount = 5; + int[] coins = {1, 2, 5}; + System.out.println(change2(amount, coins)); + } + + + @Test + public void test1() { + int amount = 3; + int[] coins = {2}; + System.out.println(change1(amount, coins)); + } + + @Test + public void test2() { + int amount = 10; + int[] coins = {10}; + System.out.println(change1(amount, coins)); + } + + + /** + * 自己思路:由于硬币可以无限使用,因此这题是完全背包问题 ->但这题求的是方法数,因此是一个完全背包的组合问题 + * TODO 动规五部曲: + * (1) dp数组的定义: dp[i][j] 表示使用coins[0-i]的数背包容量为j时有dp[i][j]种方法可以组成 + * (2) dp数组的状态方程: dp[i][j]可以表示如果不用i硬币有几种方法组成+用i硬币有几种方法组成 + * dp[i][j]=dp[i-1][j]+dp[i-1][j-coins[i]]+dp[i-1][j-2*coins[i]]+...+ + * (3) dp数组的初始化:dp[i][0]=1;dp[0][i==coins[0]*k]=1 + * (4) dp数组的遍历顺序:外层硬币内层背包,从前往后 + * (5) 举例推导dp: 当test时 dp: + * [0 1 2 3 4 5] + * i=0: 1 1 1 1 1 1 + * i=1: 1 1 2 2 3 3 + * i=2: 1 1 2 2 3 4 + * 速度击败6.58%,内存击败5% 31ms + * + * @param amount + * @param coins + * @return + */ + public int change(int amount, int[] coins) { + + int[][] dp = new int[coins.length][amount + 1]; + + //初始化 + for (int i = 0; i < coins.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; i < amount + 1; i++) { + dp[0][i] = i % coins[0] == 0 ? dp[0][i] + 1 : 0; + } + + //动规遍历 + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < amount + 1; j++) { + if (j < coins[i]) dp[i][j] = dp[i - 1][j]; + else { + int k = 0; //表示用几次coins[i] + while (j - coins[i] * k >= 0) { + dp[i][j] += dp[i - 1][j - coins[i] * k]; + k++; + } + } + } + } + + + return dp[coins.length - 1][amount]; + } + + + /** + * 自己思路::尝试一维dp数组(可以减少一层while循环),由于硬币可以无限使用,因此这题是完全背包问题 ->但这题求的是方法数,因此是一个完全背包的组合问题 + * TODO 动规五部曲: + * (1) dp数组的定义: dp[j] 表示使用coins[0-i]的数背包容量为j时有dp[j]种方法可以组成 + * (2) dp数组的状态方程: dp[j]可以表示如果不用i硬币有几种方法组成+用i硬币有几种方法组成 + * dp[j]+=dp[j-coins[i]] + * (3) dp数组的初始化:dp[0]=1;dp[i==coins[0]*k]=1 + * (4) dp数组的遍历顺序:外层硬币内层背包,从前往后 + * (5) 举例推导dp:当test时 dp: + * [0 1 2 3 4 5] + * i=0: 1 1 1 1 1 1 + * i=1: 1 1 2 2 3 3 + * i=2: 1 1 2 2 3 4 + * 速度击败99.93%,内存击败34.58% 2ms + * + * @param amount + * @param coins + * @return + */ + public int change1(int amount, int[] coins) { + + int[] dp = new int[amount + 1]; + //初始化 + dp[0]=1;//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装 + //动规遍历 + for (int i = 0; i < coins.length; i++) { + for (int j = coins[i]; j < amount + 1; j++) { + dp[j]+=dp[j-coins[i]]; //少用coins[i]的硬币的方法之和 + } + } + return dp[amount]; + } + + /** + * 这种把背包容量放在外面的情况就有可能出现重复的情况:因为背包容量的每一个值,都是经过所有coins[i] 的计算,包含了{1, 5} 和 {5, 1}两种情况。 + * TODO 这种是求的排列数而不是组合数 + * 打印其dp数组可以发现,对于test来说,其dp数组为:1 1 2 3 5 9 + * + * @param amount + * @param coins + * @return + */ + public int change2(int amount, int[] coins) { + + int[] dp = new int[amount + 1]; + //初始化 + dp[0]=1;//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装 + //动规遍历 + for (int j = 0; j <= amount; j++) { // 遍历背包容量 + for (int i = 0; i < coins.length; i++) { // 遍历物品 + if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]]; + //TODO 为什么这里会出现{1, 5} 和 {5, 1}两种情况? + // 核心在于coins在里面这层循环,而dp[j] += dp[j - coins[i]];又用到了之前的dp[j],那么以前的dp[j]可是计算过coins【1和5】的情况,这里再conin[5和1]的情况就会重复 + // 而在外面这层循环就不会出现这种情况,因为coins是按顺序遍历的,这里的dp就只能考虑coin[1]在考虑coin[5] + } + } + return dp[amount]; + } + + +}