最优硬币组合问题

136 阅读3分钟

这道题是一个经典的零钱兑换问题,通常使用动态规划(Dynamic Programming, DP)来解决。动态规划是一种将问题分解成更小的子问题,并逐步构建解决方案的方法。在零钱兑换问题中,我们需要计算能够凑出给定总金额所需的最少硬币数量。

题目描述

小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道如何使用最少数量的硬币凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。

示例

  • 输入:coins = [1, 2, 5], amount = 18
  • 输出:5(可以使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币)

解决方案

1. 定义状态

  • 我们用 dp[i] 表示凑成总金额 i 所需的最少硬币数量。

2. 状态转移方程

  • 对于每个硬币面值 coin,如果 i 大于等于 coin,则更新 dp[i]dp[i - coin] + 1,其中 dp[i - coin] 表示凑成金额 i - coin 所需的最少硬币数量加上一个 coin 硬币。

3. 初始化

  • dp[0] 初始化为0,因为凑成金额0所需的硬币数量为0。
  • 其他 dp[i] 初始化为一个大值(比如 amount + 1),表示初始状态下无法凑成这些金额。

4. 返回结果

  • 最终返回 dp[amount],如果 dp[amount] 大于 amount,则表示无法凑成该金额,返回 -1,否则返回 dp[amount]

代码实现

以下是完整的Java代码实现:

java

import java.util.Arrays;

public class CoinChange {
    public static void main(String[] args) {
        int[] coins = {1, 2, 5};
        int amount = 18;
        System.out.println(minCoins(coins, amount));  // 输出应该是5
    }

    public static int minCoins(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);  // 用一个比 `amount` 大的值来初始化
        dp[0] = 0;  // 凑出金额 0 需要的硬币数量为0

        for (int i = 1; i <= amount; i++) {
            for (int coin : coins) {
                if (i >= coin) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        return dp[amount] > amount ? -1 : dp[amount];
    }
}

代码解释

  1. 初始化

    • 创建一个长度为 amount + 1 的数组 dp,并将所有值初始化为一个大于 amount 的值(这里是 amount + 1)。
    • 设置 dp[0] = 0,表示凑出金额0所需的硬币数量为0。
  2. 状态转移

    • 使用双层循环,外层循环遍历从1到 amount 的每一个金额,内层循环遍历每一个硬币面值 coin
    • 对于每一个金额 i,如果 i 大于等于 coin,则更新 dp[i]dp[i - coin] + 1dp[i] 中的最小值。
  3. 返回结果

    • 最终结果存储在 dp[amount] 中,如果 dp[amount] 的值大于 amount,则表示无法凑出该金额,返回 -1,否则返回 dp[amount]

复杂度分析

  • 时间复杂度

    • 外层循环遍历从1到 amount,内层循环遍历每一个硬币面值,因此时间复杂度为 O(amount * coins.length)
  • 空间复杂度

    • 使用了一个长度为 amount + 1 的数组 dp,因此空间复杂度为 O(amount)

总结

通过动态规划的方法,我们可以有效地解决零钱兑换问题。动态规划的核心思想是将问题分解为更小的子问题,并逐步构建解决方案。在这个问题中,通过定义状态和状态转移方程,我们能够计算出凑成给定总金额所需的最少硬币数量。

这个解决方案的时间复杂度为 O(amount * coins.length),空间复杂度为 O(amount),能够高效地解决大规模数据的处理。