Loading

322. [动态规划]零钱兑换

322. 零钱兑换

该问题存在最优子结构,当前想求的amount为一个具体值,你可以得知凑出amount-coins[i]的最少硬币数,又因为硬币的数量是没有限制的,所以所有子问题之间是不存在相互制约的,是相互独立的。

那么,问题的基线条件(base case)即为目标金额amount为0时,算法返回0,因为此时不需要任何数量的硬币就已经凑出目标金额了。

在原问题分解成若干子问题时,其中变化的变量是amount,因为只有amount会不断地向base case靠近。那么,每选择一枚硬币,就将改变amount

那么dp函数的定义,我们可以理解为:题目要求我们计算凑出目标金额所需的最少硬币数量。所以我们可以这样定义 dp 函数:

dp(n) 的定义:输入一个目标金额 n,返回凑出目标金额 n 的最少硬币数量。

这里给出dp函数所描述的状态转移方程:

\[dp(n)= \begin{cases} -1, \text{n < 0} \\ 0, \text{n = 0} \\ \min \{dp(n-coin) + 1|coin \in coins\}, \text{n > 0} \end{cases} \]

方法一:记忆化搜索+动态规划

// 执行耗时:190 ms,击败了5.00% 的Java用户
// 内存消耗:39.7 MB,击败了5.01% 的Java用户

class Solution {
    public static HashMap<Integer, Integer> memo;

    public int coinChange(int[] coins, int amount) {
        memo = new HashMap<>();
        return dp(amount, coins);
    }

    public int dp(int n, int[] coins){
        // 查询备忘录,避免重复计算
        if (memo.containsKey(n)){
            return memo.get(n);
        }
        // base case
        if (n == 0){
            return 0;
        }
        if (n < 0){
            return -1;
        }
        int res = Integer.MAX_VALUE;
        for (int coin: coins){
            int leftCoins = dp(n - coin, coins);
            if (leftCoins == -1) {
                continue;
            }
            res = Math.min(res, 1 + leftCoins);
        }
        memo.put(n, res != Integer.MAX_VALUE ? res : -1);
        return memo.get(n);
    }
}

方法二:动态规划数组迭代

当然,我们也可以自底向上使用 dp table 来消除重叠子问题,关于「状态」「选择」和 base case 与之前没有区别,dp 数组的定义和刚才 dp 函数类似,也是把「状态」,也就是目标金额作为变量。不过 dp 函数体现在函数参数,而 dp 数组体现在数组索引:

dp 数组的定义:当目标金额为i时,至少需要dp[i]枚硬币凑出。

// 执行耗时:14 ms,击败了86.50% 的Java用户
// 内存消耗:37.9 MB,击败了85.06% 的Java用户

class Solution {
    public int coinChange(int[] coins, int amount) {
        // dp数组大小为 amount + 1, 初始值也为 amount + 1
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        for (int i = 0; i < dp.length; i++){
            for (int coin: coins) {
                if (i - coin < 0) continue;
                dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
            }
        }
        return (dp[amount] == amount + 1) ? -1 : dp[amount];
    }
}
posted @ 2020-11-04 10:08  上海井盖王  阅读(88)  评论(0)    收藏  举报