动态规划篇-05:零钱兑换

发布时间:2024年01月15日

322、零钱兑换

我们来继续使用我们的框架。

状态转移方程

base case

当总金额数为0的时候,所需硬币数为0.

明确状态

“原问题和子问题中会变化的变量”

根据题意,问题中的变量就是”最少硬币数“。

确定选择

“导致状态变化的行为”

在这里就是”选择不同面额的硬币“

定义dp函数

一般来说,都是题中问什么,dp函数设成什么。在这里定义dp函数为:

dp(n) : 凑成总金额 n 所需的硬币数。


接下来我们就可以写出状态转移方程,同时记得使用 [分解问题] 的思想。

dp(n) = dp(n - i) + 1 // 凑成金额n所需硬币数 =? 凑成金额(n-i)所需硬币数 + 选用的面值为i的硬币

在实际使用中,因为做选择时是从硬币集合中做选择,所以要将硬币集合也作为一个参数。?


暴力递归

class Solution {
    public int coinChange(int[] coins, int amount) {
        return dp(coins, amount);
    }
    //dp函数返回值的定义:凑出金额amount的硬币数
    public int dp(int[] coins, int amount){
        //base case
        if(amount == 0){
            return 0;
        }
        if(amount < 0){
            return -1;
        }
        int res = Integer.MAX_VALUE;
        //遍历选择列表做选择
        for(int coin : coins){
            int subProblem = dp(coins,amount - coin);
            if(subProblem == -1){
                continue;
            }
            //寻找最优解
            res = Math.min(res,subProblem + 1);
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

使用备忘录的从上到下的递归解法

class Solution {
    int[] memo;

    public int coinChange(int[] coins, int amount) {
        memo = new int[amount + 1];
        Arrays.fill(memo,-666);
        return dp(coins,amount);
    }
    int dp(int[] coins, int amount){
        /*使用备忘录数组后,base case 出现了新的条件*/
        if (amount == 0) return 0;
        if (amount < 0) return -1;
        if (memo[amount] != -666){
            return memo[amount];
        }
        /*接下来的判定照旧*/
        int res = Integer.MAX_VALUE;
        for(int coin : coins){
            int subProblem = dp(coins, amount - coin);
            if (subProblem == -1) continue;
            res = Math.min(res,subProblem + 1);
        }
        /*将计算结果存入备忘录*/
        memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
        return memo[amount];
    }
}

使用dp数组的自下而上的迭代解法

class Solution {
    int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);

        // base case
        dp[0] = 0;
        // 外层 for 循环在遍历所有状态的所有取值
        /*外层循环什么意思?是金额!
        dp数组的下标是金额,dp数组的元素是硬币数*/
        for (int i = 0; i < dp.length; i++) {
            // 内层 for 循环在求所有选择的最小值
            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];
    }
}

问题1:为什么数组初始化为“amount + 1”?

为啥 dp 数组中的值都初始化为 amount + 1 呢,因为凑成 amount 金额的硬币数最多只可能等于 amount(全用 1 元面值的硬币),所以初始化为 amount + 1 就相当于初始化为正无穷,便于后续取最小值。

为啥不直接初始化为 int 型的最大值 Integer.MAX_VALUE 呢? 因为后面有 dp[i - coin] + 1,这就会导致整型溢出。

文章来源:https://blog.csdn.net/From_C/article/details/135592636
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。