Skip to content

Commit cb720f7

Browse files
committed
零钱兑换,完全背包,一二维dp
1 parent 80d6b63 commit cb720f7

File tree

2 files changed

+123
-19
lines changed

2 files changed

+123
-19
lines changed

leetcode_Java/Solution0322.java

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,77 @@ private int dfs(int[] memo, int[] coins, int amount) {
4141

4242

4343
/*
44-
动态规划:完全背包,先物品再背包是计算组合,先背包再物品是计算排列,本题物品背包顺序无关,必须正序遍历背包才能重复使用物品
44+
动态规划:完全背包,二维数组
4545
1、题目:给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。你可以认为每种硬币的数量是无限的。
4646
2、题目简化:求凑成总金额amount所需的最少硬币数
47-
3、定义dp数组:dp[i] 表示凑成总金额i所需的最少硬币数
48-
4、状态转移方程:dp[i] = Math.min(dp[i], dp[i-coin]+1); 需要比较多种硬币情况下的最小值
47+
3、定义dp数组:dp[i][j] 表示使用前i个硬币,凑成总金额j所需的最少硬币数
48+
4、状态转移方程
49+
if (j - coins[i - 1] >= 0) dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1); // 金额足够,“不用”和“用”两种情况比较出最少
50+
else dp[i][j] = dp[i - 1][j]; // 金额不够,只能选择“不用”该硬币
51+
注意:dp[i - 1][j - coins[i - 1]] 表示第i个硬币只用一次,属于0-1背包。dp[i][j - coins[i - 1]] 表示第i个硬币重复使用,属于完全背包。
4952
5、初始化:
53+
1)首列 dp[i][0] 表示使用前i个硬币,凑成总金额j所需的最少硬币数为0
54+
2)其他填充一个不可能的硬币数,使得不影响计算,也方便状态转移时取最小值
55+
6、遍历dp数组填表:第一层遍历硬币,第二层遍历金额,都从1开始,正序遍历。两个for循环先后都可以,因为计算当前状态只需要当前行左边的状态和上一行的状态
56+
7、返回结果:最后一个状态就是结果
57+
58+
二维dp更新过程,从左到右,从上到下
59+
coins = [1, 2, 5], amount = 11
60+
0 12 12 12 12 12 12 12 12 12 12 12
61+
0 1 2 3 4 5 6 7 8 9 10 11
62+
0 1 1 2 2 3 3 4 4 5 5 6
63+
0 1 1 2 2 1 2 2 3 3 2 3
64+
*/
65+
class Solution {
66+
public int coinChange(int[] coins, int amount) {
67+
int n = coins.length;
68+
int[][] dp = new int[n + 1][amount + 1];
69+
for (int i = 0; i <= n; i++) {
70+
for (int j = 1; j <= amount; j++) {
71+
dp[i][j] = amount + 1;
72+
}
73+
}
74+
for (int i = 1; i <= n; i++) {
75+
for (int j = 1; j <= amount; j++) {
76+
if (j - coins[i - 1] >= 0) {
77+
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1);
78+
} else {
79+
dp[i][j] = dp[i - 1][j];
80+
}
81+
}
82+
}
83+
return (dp[n][amount] == amount + 1) ? -1 : dp[n][amount];
84+
}
85+
}
86+
87+
88+
/*
89+
动态规划:完全背包,一维数组
90+
1、定义dp数组:dp[i] 表示凑成总金额i所需的最少硬币数
91+
2、状态转移方程:dp[i] = Math.min(dp[i], dp[i-coin]+1); 需要比较多种硬币情况下的最小值
92+
3、初始化:
5093
dp[i]=amount+1; 填充一个不可能的硬币数,使得不影响计算,也方便状态转移时取最小值
5194
dp[0]=0; 表示凑成总金额0所需的最少硬币数为0,方便后面推导
52-
6、遍历dp数组填表:第一个for循环遍历dp数组的未知位置,第二个for循环遍历硬币数组,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
53-
7、返回结果:最后一个状态就是结果
95+
4、遍历dp数组填表:
96+
1)第一个for循环遍历dp数组的未知位置,第二个for循环遍历硬币数组,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
97+
2)先物品再背包是计算组合,先背包再物品是计算排列,本题物品背包顺序无关,必须正序遍历背包才能重复使用物品
98+
5、返回结果:最后一个状态就是结果
99+
100+
一维dp更新过程,从左到右
101+
coins = [1, 2, 5], amount = 11
102+
0 12 12 12 12 12 12 12 12 12 12 12
103+
1 2 3 4 5 6 7 8 9 10 11
104+
1 2 2 3 3 4 4 5 5 6
105+
1 2 2 3 3 2 3
54106
*/
55107
class Solution {
56108
public int coinChange(int[] coins, int amount) {
57109
int[] dp = new int[amount + 1];
58110
Arrays.fill(dp, amount + 1);
59111
dp[0] = 0;
60-
for (int i = 1; i <= amount; i++) {
61-
for (int coin : coins) {
62-
if (i - coin < 0) {
63-
continue;
64-
}
65-
dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
112+
for (int coin : coins) {
113+
for (int i = coin; i <= amount; i++) {
114+
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
66115
}
67116
}
68117
return (dp[amount] == amount + 1) ? -1 : dp[amount];
@@ -78,9 +127,11 @@ public int coinChange(int[] coins, int amount) {
78127
int[] dp = new int[amount + 1];
79128
Arrays.fill(dp, amount + 1);
80129
dp[0] = 0;
81-
for (int coin : coins) {
82-
for (int i = coin; i <= amount; i++) {
83-
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
130+
for (int i = 1; i <= amount; i++) {
131+
for (int coin : coins) {
132+
if (i - coin >= 0) {
133+
dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
134+
}
84135
}
85136
}
86137
return (dp[amount] == amount + 1) ? -1 : dp[amount];

leetcode_Java/Solution0518.java

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,67 @@
22

33

44
/*
5-
动态规划:完全背包,先物品再背包是计算组合,先背包再物品是计算排列,必须正序遍历背包才能重复使用物品
5+
动态规划:完全背包,二维数组
66
1、题目:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。
77
2、题目简化:求凑成总金额amount的硬币组合数
8-
3、定义dp数组:dp[i] 表示凑成总金额i的硬币组合数
9-
4、状态转移方程:dp[i] += dp[i-coin],即所有dp[i-coin]相加
10-
5、初始化:dp[0] = 1,表示凑成总金额0的硬币组合数为1,方便后面推导
11-
6、遍历dp数组填表:第一个for循环遍历硬币数组,第二个for循环遍历dp数组的未知位置,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
8+
3、定义dp数组:dp[i][j] 表示使用前i个硬币,凑成总金额j的硬币组合数
9+
4、状态转移方程
10+
if (j - coins[i - 1] >= 0) dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]]; // 金额足够,“不用”和“用”两种情况相加
11+
else dp[i][j] = dp[i - 1][j]; // 金额不够,只能选择“不用”该硬币
12+
注意:dp[i - 1][j - coins[i - 1]] 表示第i个硬币只用一次,属于0-1背包。dp[i][j - coins[i - 1]] 表示第i个硬币重复使用,属于完全背包。
13+
5、初始化:首行首列
14+
dp[i][0] = 1 表示使用前i个硬币,凑成总金额0的硬币组合数为1,即不用硬币就能凑成
15+
dp[0][j] = 0 表示使用前0给硬币,凑成总金额j的硬币组合数为0,即没有硬币可以凑成。创建dp数组时已经有默认值
16+
6、遍历dp数组填表:第一层遍历硬币,第二层遍历金额,都从1开始,正序遍历。两个for循环先后都可以,因为计算当前状态只需要当前行左边的状态和上一行的状态
1217
7、返回结果:最后一个状态就是结果
18+
19+
二维dp更新过程,从左到右,从上到下
20+
amount = 5, coins = [1, 2, 5]
21+
1 0 0 0 0 0
22+
1 1 1 1 1 1
23+
1 1 2 2 3 3
24+
1 1 2 2 3 4
25+
*/
26+
class Solution {
27+
public int change(int amount, int[] coins) {
28+
int n = coins.length;
29+
int[][] dp = new int[n + 1][amount + 1];
30+
for (int i = 0; i <= n; i++) {
31+
dp[i][0] = 1;
32+
}
33+
for (int i = 1; i <= n; i++) {
34+
for (int j = 1; j <= amount; j++) {
35+
if (j - coins[i - 1] >= 0) {
36+
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
37+
} else {
38+
dp[i][j] = dp[i - 1][j];
39+
}
40+
}
41+
}
42+
return dp[n][amount];
43+
}
44+
}
45+
46+
47+
/*
48+
动态规划:完全背包,一维数组
49+
1、定义dp数组:dp[i] 表示凑成总金额i的硬币组合数
50+
2、状态转移方程:dp[i] = dp[i] + dp[i-coin],即所有dp[i-coin]相加
51+
3、初始化:dp[0] = 1,表示凑成总金额0的硬币组合数为1,即不用硬币,方便后面推导
52+
4、遍历dp数组填表:
53+
1)遍历顺序:
54+
① 第一个for循环遍历硬币数组,第二个for循环遍历dp数组的未知位置,根据条件获取dp数组的已知位置,根据状态转移方程取已知结果汇总计算未知结果
55+
② 先物品再背包是计算组合,先背包再物品是计算排列,必须正序遍历背包才能重复使用物品
56+
2)遍历理解
57+
① 一维数组是滚动数组,每一轮滚动遍历中,未遍历的表示上一轮的旧状态,正在遍历的表示正在计算的状态,遍历过的表示本轮的新状态
58+
② 本轮遍历中,金额不够时,只能选择不用硬币,旧状态就是“不用”,所以只遍历金额足够的情况即可(i = coin 开始)
59+
5、返回结果:最后一个状态就是结果
60+
61+
一维dp更新过程,从左到右
62+
amount = 5, coins = [1, 2, 5]
63+
1 1 1 1 1 1
64+
2 2 3 3
65+
4
1366
*/
1467
class Solution {
1568
public int change(int amount, int[] coins) {

0 commit comments

Comments
 (0)