【Leetcode】322. 零钱兑换

118 阅读1分钟

题目描述

在这里插入图片描述

// 322. 零钱兑换

// 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以
// 凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返
// 回 -1。

// 你可以认为每种硬币的数量是无限的。

题解


// 递归搜索
// 时间复杂度太高
class Solution {
	int res = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
		search(coins, amount, 0);
        if (res == Integer.MAX_VALUE)
            return -1;
		return res;
	}

    public void search(int[] coins, int amount, int count) {
		if (amount < 0)
			return;
		if (amount == 0)
			res = Math.min(res, count);
		for (int i = 0; i < coins.length; i++) {
			search(coins, amount - coins[i], count + 1);
		}
    }
}


// 动态规划
// 
// 创建动态规划数组dp,dp[i]表示组成i金额的最少硬币数。我们要求dp[amount],
// 所以dp长度为amount + 1,所以组成金额0的硬币数为dp[0]=0。
// 在此之前,初始化dp其他位置(不要初始化为Integer.MAX_VALUE,会溢出),
// 硬币最少面额是1,所以要组成amount金额的最大硬币数就等于amount,
// 所以amount+1是一个永远到不了的数字,我们就初始化为dp的其他位置为amount+1,
// 
// 要求组成i金额的最少硬币数dp[i],dp[i]就等于上一次组成金额的最少硬币数
// dp[?]加1,假设 ? 还差一枚硬币coins[j]就得到i,根据金额关系我们有:
// dp[?] = dp[i - coins[j]]。所以有:
// dp[i] = dp[i - coins[j]] + 1
// 因为要最少的硬币数,所以贪心地取最小值更新dp[i]:
// dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1)
// 我们要求dp[i],就要求i之前的所有状态,所以i有for(i = 0; i <= amount; i++)
// 硬币能用无限次,所以coins[j]要在第二层for循环中反复取coins元素,
// j有:for(j = 0; j < coins.length; j++)
// 
// for循环从0到amount遍历dp的索引,记为i,
// 遍历每个状态组成i金额的最少硬币数为dp[i],因为硬币数量是无限的,
// 所以第二层for循环,我们从头到尾遍历coins元素记为coins[j],
// 如果coins[j]小于i,则coins[j]可以用来凑i金额,dp[i]状态由
// dp[i]和dp[i - coins[j]] + 1中的最小值决定,
// 最后遍历完之后查看dp[amount]是否大于amount,如果大于则直接返回-1,
// 否则返回dp[amount]
// 
// 执行用时:16 ms, 在所有 Java 提交中击败了59.99%的用户
// 内存消耗:37.9 M, 在所有 Java 提交中击败了49.30%的用户
// 
class Solution {
    public int coinChange(int[] coins, int amount) {
		int max = amount + 1;
		int[] dp = new int[amount + 1];
		Arrays.fill(dp, amount + 1);
		dp[0] = 0;
		for (int i = 1; i < amount + 1; i++) {
			for (int j = 0; j < coins.length; j++) {
				if (coins[j] <= i) {
					dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
				}
			}
		}
		return (dp[amount] > amount) ? -1 : dp[amount];
	}
}