322、零钱兑换

  1. 322、零钱兑换
    1. 示例 1:
    2. 示例 2:
  • 题解
    1. 1、回溯法——递归实现
    2. 2、动态规划
    3. 3、排序+深度优先搜索
  • 322、零钱兑换

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

    示例 1:

    输入: coins = [1, 2, 5], amount = 11
    输出: 3 
    解释: 11 = 5 + 5 + 1
    

    示例 2:

    输入: coins = [2], amount = 3
    输出: -1
    
    • 说明: 你可以认为每种硬币的数量是无限的。

    题解

    1、回溯法——递归实现

    $$
    \min_{x} \sum_{i=0}^{n - 1} x_i \ \text{subject to} \sum_{i=0}^{n - 1} x_i*c_i = S
    $$

    // java
    class Solution {
        public int coinChange(int[] coins, int amount) {
            // 构造回溯函数求解,参数:处理到硬币的下标,硬币列表,目标值
            return coinChange(0,coins,amount);
        }
    
        private int coinChange(int idxCoin, int[] coins, int amount) {
            // 外围,边界片判断
            // 求解完成
            if (amount == 0) {
                return 0;
            }
            // 求解未完成, 没有遍历完全部硬币 且 目标值 ≥ 0
            if(idxCoin < coins.length && amount > 0) {
                int maxVal = amount/coins[idxCoin];
                int minCost = Integer.MAX_VALUE;
                // 对每一枚硬币的面值,进行枚举遍历
                for (int i = 0;i <= maxVal;i++) {
                    // 递归求解,深度优先搜索
                    int res = coinChange(idxCoin+1,coins,amount- i * coins[idxCoin]);
                    // 存在有效解,更新最小花费
                    if (res != -1) {
                        minCost = Math.min(minCost,res + i);
                    }
                }
                // 返回最优结果
                return (minCost == Integer.MAX_VALUE)? -1 : minCost;
            }
            // 求解结束
            return -1;
        }
    }
    

    2、动态规划

    • 自上而下
      • 从目标值出发求解,知道目标值变为0
    • 代码:依然用回溯法实现
    // java
    class Solution {
        public int coinChange(int[] coins, int amount) {
            // 异常处理
            if (amount < 1) return 0;
            // 调用回溯函数,硬币列表,硬币总数,记忆化数组(减少不必要的计算)
            return coinChange(coins,amount,new int[amount]);
        }
    
        private int coinChange(int[] coins,int rem, int[] count){
            // 临界处理
            if (rem < 0) return -1;
            if (rem == 0) return 0;
            // 存在已经求出来的解,直接使用
            if (count[rem - 1] != 0) {
                return count[rem - 1];
            }
            // 记录最小值
            int min = Integer.MAX_VALUE;
            // 求当前rem的解
            for (int coin : coins) {
                // 递归,更新剩余的总值
                int res =  coinChange(coins,rem - coin,count);
                // 条件更新
                if (res != -1 && res < min) {
                    min = 1 + res;
                }
            }
            // 将结果记录下来
            count[rem - 1] = (min == Integer.MAX_VALUE)? -1 : min;
            // 返回结果
            return count[rem - 1];
        }
    }
    
    • 自下而上——真正的动态规划
      • 从0出发,直到到达目标值
      • $F(i) = \min_{j=0…n-1}F(i - c_i) + 1$
    • 代码
    // java
    class Solution {
        public int coinChange(int[] coins, int amount) {
            // dp数组初值
            int max = amount + 1;
            // dp状态数组
            int[] dp =  new int[amount];
            // 填充数组
            Arrays.fill(dp,max);
            dp[0] = 0;
            // 遍历状态数组
            for (int i = 1;i <= amount;i++) {
                // 遍历硬币数组
                for (int j = 0; j < coins.length;j++) {
                    // 处理有效硬币,更新最小值
                    if (i >= coins[j]) {
                        dp[i] = Math.min(dp[i],dp[i - coins[j]] + 1);
                    }
                }
            }
            // 返回结果
            return dp[amount] > amount ? -1 : dp[amount];
        }
    }
    

    3、排序+深度优先搜索

    class Solution {
        private int min = Integer.MAX_VALUE;
    
        public int coinChange(int[] coins, int amount) {
            // 排序
            Arrays.sort(coins); // asc
            // 递归
            dfs(coins, coins.length - 1, amount, 0);
            return min == Integer.MAX_VALUE ? -1 : min;
        }
    
        private void dfs(int[] coins, int ci, int rest, int cnt) {
            if (ci < 0) return;
            // 从大到小枚举可能的每一枚硬币值
            for (int i = rest / coins[ci]; i >= 0; i--) {
                // 获取剩下的待兑换的金额 以及 当前兑换的硬币数量
                int currRest = rest - i * coins[ci], currCnt = cnt + i;
                // 零钱没有兑换完 且 当前兑换可能是最小数量兑换
                if (currRest > 0 && currCnt + 1 < min) dfs(coins, ci - 1, currRest, currCnt);
                else { 
                    // 更新最少的硬币数
                    if (currRest == 0 && currCnt < min) min = currCnt;
                    // 否则一定无解
                    break;
                }
            }
        }
    }
    

    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1056615746@qq.com

    💰

    Title:322、零钱兑换

    Count:913

    Author:攀登

    Created At:2020-07-26, 00:19:44

    Updated At:2024-06-15, 15:52:32

    Url:http://jiafeimao-gjf.github.io/2020/07/26/322%E3%80%81%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2/

    Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.

    ×

    Help us with donation