动态规划算法计算硬币组合

这篇博客探讨了一道Lintcode上的 CoinChange 问题,即如何使用三种面值为2元、5元和7元的硬币,以最少的硬币数凑够27元。博主提供了两种动态规划的解决方案:自底向上和自顶向下。在自底向上的方法中,博主通过创建一个数组f来存储不同总额所需的最小硬币数量,并逐步更新数组以找到最小值。自顶向下则通过递归实现,每次尝试所有可能的硬币组合,寻找最优解。这两种方法都确保了不需要找零。
摘要由CSDN通过智能技术生成

 你有三种硬币,分别面值2元,5元和7元,每种硬币都有足够多。买一本书需要27元。如何用最少的硬币组合正好付清,不需要对方找钱?

这道题是lintcode编号669的Coin Change问题。

实现方式有两个钟,一种是自底向上推导方法,另一种是自顶向下推导。

自底向上:

package leecode.daynamicprogramming;

public class CoinChange {

    public static void main(String[] args) {
        int[] a = {2,5,7};
        int ret = coinChange(a, 9);
        System.out.println(ret);
    }
    public static int coinChange(int[] A, int M) {
        /*
        * 原理:
        * 1. 求一个给定值的最小数量硬币组合,可以将问题拆分成如下
        *
        *   A = [2,5,7]  // M = 27
        *   f[X] = min{f[X-2]+1, f[X-5]+1, f[X-7]+1}
        *  他利用的原理是假设获得总额的组合,最后一个硬币币值是n, 总额是m, 那最小硬币数量一定是f(m) = f(m-n) + 1;
        * 我们将最后一个硬币的币值全部枚举一遍,比如币值有2,5,7三种情况,总额27,这样我们可以获取三种可能,从中选一个最小值,就是我们要的
        * 结果。
        * 已给出示例为例,几种特殊情况:
        * f(0)总是等于0
        * f(1)无法得到货币组合,我们给出值是无穷大(程序中用Integer.MAX_VALUE代表)
        * f(2)=1
        * f(3)业务无法得到组合,所以也是无穷大
        * 以此类推
        * 

        */

        //f(x)代表给定硬币总额为x,求总和为x的最小硬币组合(硬币数量最少),这里用一个数组f代表。
        // 每一个数组下标表示每一种总额, 对应的数组的值表示对应的组成该数额的硬币数量。
        int[] f = new int[M + 1];
        //边界条件: 总额为0,需要硬币的数量为0,所以f[0]=0。如果总额无论怎么用给定货币组合,都达不到,则f(x)=Integer.MAX_VALUE
        f[0] = 0;
        int n = A.length;
        // 硬币的种类
        // 初始化, 0个硬币  f[0] = 0;
        // f[1], f[2], ... , f[27] = Integer.MAX_VALUE
        //这里可以不显示指定f[0] = 0, jvm默认会给整形数组初始化为0

        for (int i = 1; i <= M; i++) {
            f[i] = Integer.MAX_VALUE;
        }
        for (int i = 1; i <= M; i++) {
            // 使用第j个硬币 A[j]    // f[i] = min{f[i-A[0]]+1, ... , f[i-A[n-1]]+1}
            //从低到高,逐一计算f(x)的值,后边的值会利用前边算出的值。
            for (int j = 0; j < n; ++j) {
                // 如果通过放这个硬币能够达到重量i
                /*f[i - A[j]] = Integer.MAX_VALUE表示最后一枚硬币是A[j],且总额减去该枚硬币的值,可能的硬币数量组合是.MAX_VALUE
                * 的情况,也即总额减掉最后一枚币值A[j],用任何货币组合都无法实现,这种情况就没必要再更改f[i]的值了*/
                
                if (i >= A[j] && f[i - A[j]] != Integer.MAX_VALUE) {
                    //在遍历每一个硬币的时候,f(i)可能会变化,总是获取当前f(i)和f(i-A[j])+1中最小的哪一个当作最新的f(i)的值
                    f[i] = Math.min(f[i - A[j]] + 1, f[i]);
                }
            }
        }
        if (f[M] == Integer.MAX_VALUE) {
            return -1;
        }
        return f[M];
    }
}


自底向上另一种写法:

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; 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];
    }
}

 自顶向下:


public 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;
        for (int coin : coins) {
            int res = coinChange(coins, rem - coin, count);
            if (res >= 0 && res < min) {
                min = 1 + res;
            }
        }
        count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
        return count[rem - 1];
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值