7 个月前
这道题类似于完全背包问题,每个物品都可以无限使用,但是要求背包必须装满,而且要求背包中的物品数目最少, 归纳为数学问题就是,
- v[i]:代表每种硬币的价值
- x[i]:代表每种硬币拿的个数,0<=x[i]<=amount/v[i]
- 所求问题可以归纳为:
- 在满足:amount=v1x1+v2x2+v3x3+...+vnxn 的条件下
- 求: target=min{x1+x2+x3+....xn}
- 最简单的一种思路就是把所有{xi}的组合全部拿出来,然后让target最小即可,利用递归就可以解决问题,但是时间复杂度会很高,但是如果有好的剪枝策略,也可以使用
- 另外一种方法就是常规的动态规划,利用一个amout+1长度的dp数组,记录每一个状态的最优解,过程见程序和注释
public int coinChange(int[] coins, int amount) {
if(coins.length == 0)
return -1;
//声明一个amount+1长度的数组dp,代表各个价值的钱包,第0个钱包可以容纳的总价值为0,其它全部初始化为无穷大
//dp[j]代表当钱包的总价值为j时,所需要的最少硬币的个数
int[] dp = new int[amount+1];
Arrays.fill(dp,1,dp.length,Integer.MAX_VALUE);
//i代表可以使用的硬币索引,i=2代表只在第0个,第1个,第2个这三个硬币中选择硬币
for (int i = 0; i < coins.length; i++) {
/**
* 当外层循环执行一次以后,说明在只使用前i-1个硬币的情况下,各个钱包的最少硬币个数已经得到,
* 有些钱包的值还是无穷大,说明在仅使用前i-1个硬币的情况下,不能凑出钱包的价值
* 现在开始再放入第i个硬币,要想放如w[i],钱包的价值必须满足j>=w[i],所以在开始放入第i个硬币时,j从w[i]开始
*/
for (int j = coins[i]; j <= amount; j++) {
/**
* 如果钱包当前的价值j仅能允许放入一个w[i],那么就要进行权衡,以获得更少的硬币数
* 如果放入0个:此时钱包里面硬币的个数保持不变: v0=dp[j]
* 如果放入1个:此时钱包里面硬币的个数为: v1=dp[j-coins[i]]+1
* 【前提是dp[j-coins[i]]必须有值,如果dp[j-coins[i]]是无穷大,说明无法凑出j-coins[i]价值的钱包,
* 那么把w[i]放进去以后,自然也凑不出dp[j]的钱包】
* 所以,此时当钱包价值为j时,里面的硬币数目为 dp[j]=min{v0,v1}
* 如果钱包当前价值j能够放入2个w[i],就要再进行一次权衡
* 如果不放人第2个w[i],此时钱包里面硬币数目为,v1=dp[j]=min{v0,v1}
* 如果放入第2个w[i], 此时钱包里面硬币数目为,v2=dp[j-coins[i]]+1
* 所以,当钱包的价值为j时,里面的硬币数目为dp[j]=min{v1,v2}=min{v0,v1,v2}
* 钱包价值j能允许放入3个,4个.........w[i],不断更新dp[j],最后得到在仅使用前i个硬币的时候,每个钱包里的最少硬币数目
*/
if(dp[j-coins[i]] != Integer.MAX_VALUE) {
dp[j] = Math.min(dp[j], dp[j-coins[i]]+1);
}
}
}
if(dp[amount] != Integer.MAX_VALUE)
return dp[amount];
return -1;
}