leetcode: 322. 零钱兑换
题目描述
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
解题思路
朴素解法
定义dp[i][j]为使用前i个硬币,凑成总金额为j,需要的最少的硬币数量。
那么,显然 状态转移可以表示为:dp[i][j] = min(dp[i-1][j], dp[i-1][j-v]+1, ..., dp[i-1][j-k*v]+k)
class Solution {
int INF = 0x3f3f3f3f;
public int coinChange(int[] coins, int amount) {
int n = coins.length;
int[][] dp = new int[n + 1][amount + 1];
Arrays.fill(dp[0], INF); dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
int val = coins[i - 1];
dp[i][j] = dp[i - 1][j];
for (int k = 1; k * val <= j; k++) {
dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - k * val] + k);
}
}
}
return dp[n][amount] >= INF ? -1 : dp[n][amount];
}
}
一维空间优化
观察下面两个转移方程:
dp[i][j] = min(dp[i-1][j], dp[i-1][j-v]+1, ..., dp[i-1][j-k*v]+k)。
dp[i][j-v] = min(dp[i-1][j-v], ..., dp[i-1][j-k*v]+(k-1))。
显然,可以得到dp[i][j] = min(dp[i-1][j], dp[i][j-v]+1。
for (int i = 1; i <= n; i++) {
int val = coins[i - 1];
for (int j = 1; j <= amount; j++) {
dp[i][j] = dp[i - 1][j];
dp[i][j] = Math.min(dp[i][j], dp[i][j - val];
}
}
对i维度进行消除,因为dp[i-1][j]可以看作使用前i种硬币(0个第i种硬币)的情况,即dp[i][j]=dp[i-1][j],那么dp[i][j]就只取决于dp[i][j-v]。
维度消除后的状态转移方程如下,容量维度遍历顺序从小到大:
dp[j] = min(dp[j], dp[j-v]。
class Solution {
int INF = 0x3f3f3f3f;
public int coinChange(int[] coins, int amount) {
int n = coins.length;
int[] dp = new int[amount + 1];
Arrays.fill(dp, INF); dp[0] = 0;
for (int i = 1; i <= n; i++) {
int val = coins[i - 1];
for (int j = val; j <= amount; j++) {
dp[j] = Math.min(dp[j], dp[j - val] + 1);
}
}
return dp[amount] >= INF ? -1 : dp[amount];
}
}