零钱兑换 II
力扣原题链接
问题描述
给定一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0。假设每一种面额的硬币有无限个。
示例
示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
分析
这是一个典型的动态规划问题。我们需要计算可以凑成总金额的硬币组合数。可以将问题转化为两部分:背包容量为总金额amount
,物品为数组 coins
。
思考:我们是每次可以放入多个物品的背包问题,所以这是一个完全背包问题
因此,我们可以定义一个动态规划数组 dp
,其中 dp[i]
表示总金额为 i
时的组合数。
状态定义
定义一个一维动态规划数组 dp
,其中 dp[i]
表示总金额为 i
时的组合数。
状态转移方程(统计组成的种数)
对于每一个硬币 coins[j]
,我们有两种选择:使用该硬币或者不使用该硬币(抽象为选和不选)。因此状态转移方程为:
dp[i] += dp[i - coins[j]];
初始化
我们需要对动态规划数组进行初始化。初始时,总金额为 0 的组合数有 1 种方法,其余为 0。
遍历顺序
注意:这里要求的是组合数,即2+2+1和1+2+2和2+1+2都视为一种组合 (即不考虑顺序),那么需要先遍历物品,再遍历背包,这样求得就是物品的组合数了
理解:
- 先遍历物品的话,就先拿着物品1,再遍历每一个背包容量,再拿物品2,再遍历每一个背包容量,那么背包中就只会出现(1,2)这样的组合;不会把(2,1)重复算成一种情况。
- 如果先遍历背包的话,就先拿着背包1,再遍历每一个物品(物品1,物品2…),再拿背包2,再遍历物品(物品1,物品2…),那么背包中就只会出现(1,2,1,2…),那么对于(1,2)来说,就会出现(1,2)和(2,1)都各自算成一种情况了。
Java解题
class Solution {public int change(int amount, int[] coins) {// 创建一个动态规划数组 dp,其中 dp[i] 表示总金额为 i 时的组合数int[] dp = new int[amount + 1];// 初始时,总金额为 0 的组合数有 1 种方法dp[0] = 1;//写法一:for(int i = 0 ; i < coins.length; i ++){// 先遍历物品for(int j = coins[i]; j <= amount; j ++){// 在遍历背包(求物品的组合数dp [j] += dp[j-coins[i]]; // 注意这里dp数组的含义,[这里是背包容量的遍历下标]}}//写法二:// 遍历硬币数组 coinsfor (int coin : coins) {// 遍历金额从硬币面额到 amountfor (int i = coin; i <= amount; i++) {// 更新动态规划数组dp[i] += dp[i - coin]; // 注意这里dp数组的含义,[这里是背包容量的遍历下标]}}// 返回总金额为 amount 时的组合数return dp[amount];}
}
总结
通过动态规划的思想,我们可以解决这个问题。首先初始化动态规划数组,然后根据状态转移方程进行状态转移,最终返回总金额为 amount
时的组合数。