leecode更新

This commit is contained in:
markilue 2022-12-05 13:38:01 +08:00
parent a768168a5b
commit c732207ff8
1 changed files with 152 additions and 0 deletions

View File

@ -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]可是计算过coins1和5的情况这里再conin[5和1]的情况就会重复
// 而在外面这层循环就不会出现这种情况因为coins是按顺序遍历的这里的dp就只能考虑coin[1]在考虑coin[5]
}
}
return dp[amount];
}
}