硬币找零问题

时间:2022-07-22
本文章向大家介绍硬币找零问题,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

硬币找零问题是一种经典的背包问题。

顾名思义,就是你去商店买完东西,售货员会给你用若干枚硬币找钱,如何使用这些硬币完成找零。

问题一:组成当前值所需最少的硬币数目

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

示例 1:

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

输入: coins = [2], amount = 3
输出: -1


说明:
你可以认为每种硬币的数量是无限的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

该问题的一个简化版,当一个大面值的硬币总是可以由小面值的硬币组合而成时(即参考软妹币),可以使用一种贪心策略即优先使用大面值的直到不能使用再使用小面值的,如此的到的即为最少硬币花费数目。

解决方案

使用完全背包求解。

将不同面额的硬币抽象为成不同的物品,面额为物品的重量,amount为最大容量,每个物品的价值均为一,如此该问题就可以转化为求解恰好装满背包能获得最低的价值问题。

定义dp[i] [j] 为当前可以使用下标为0~i - 1的硬币,组成金额 j 的最小硬币数目。

转移方程如下:

dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i]] + 1)

具体推导感兴趣的朋友可以看我以前写的这篇博客:零一背包和完全背包

baseline:

dp[0][0] = 0\dp[0][j] = inf

其中把非法状态定义为+无穷。

实现代码如下:

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        for(int i = 1; i <= amount; i++){
            dp[i] = Integer.MAX_VALUE;
        }
        for(int i = 0; i < coins.length; i++){
            for(int j = 1; j <= amount; j++){
                if(j - coins[i] < 0 || dp[j - coins[i]] == Integer.MAX_VALUE){
                    continue;
                }
                dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
            }
        } 
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount]; 
    }
}

上述为空间压缩之后的代码。

问题二:凑成当前值的组合的数目

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:

输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:

输入: amount = 10, coins = [10] 
输出: 1


注意:

你可以假设:

0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change-2
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解决方案

还是完全背包。

题目问什么把什么定义dp,定义dp[i] [j]为当前可以使用下标为0~i - 1的硬币,组成金额 j 的方案数目。

转移方程:

dp[i][j] =max(dp[i - 1][j], dp[i - 1][j] + dp[i][j - coins[i]])

由于dp值总是大于0的

dp[i][j] =dp[i - 1][j] + dp[i][j - coins[i]]

baseline:

dp[0][0] = 1\dp[0][j] = 0

代码如下:

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for(int i = 0; i < coins.length; i++){
            for(int j = 1; j < amount + 1; j++){
                if(j - coins[i] >= 0){
                    dp[j] = dp[j] + dp[j - coins[i]];
                }
            }
        }
        return dp[amount];
    }
}

一点总结:若某个问题可以看成对一些列多个“物品”的组合问题,并且组合时对于“物品”的选择有约束,可以将该问题转化为背包问题求解。