@@ -41,28 +41,77 @@ private int dfs(int[] memo, int[] coins, int amount) {
41
41
42
42
43
43
/*
44
- 动态规划:完全背包,先物品再背包是计算组合,先背包再物品是计算排列,本题物品背包顺序无关,必须正序遍历背包才能重复使用物品
44
+ 动态规划:完全背包,二维数组
45
45
1、题目:给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。你可以认为每种硬币的数量是无限的。
46
46
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个硬币重复使用,属于完全背包。
49
52
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、初始化:
50
93
dp[i]=amount+1; 填充一个不可能的硬币数,使得不影响计算,也方便状态转移时取最小值
51
94
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
54
106
*/
55
107
class Solution {
56
108
public int coinChange (int [] coins , int amount ) {
57
109
int [] dp = new int [amount + 1 ];
58
110
Arrays .fill (dp , amount + 1 );
59
111
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 );
66
115
}
67
116
}
68
117
return (dp [amount ] == amount + 1 ) ? -1 : dp [amount ];
@@ -78,9 +127,11 @@ public int coinChange(int[] coins, int amount) {
78
127
int [] dp = new int [amount + 1 ];
79
128
Arrays .fill (dp , amount + 1 );
80
129
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
+ }
84
135
}
85
136
}
86
137
return (dp [amount ] == amount + 1 ) ? -1 : dp [amount ];
0 commit comments