动态规划解决找零钱问题

原创 2015年09月09日 19:03:54

动态规划算法通常用于求解具有某种最优性质的问题。动态规划算法与分治法类似,其基本思想都是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表(备用表)来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。

下面演示如何通过动态规划算法来解决找零钱问题,以及如何处理该问题的变种情况。

问题描述:

现存在一堆面值为 1,2,5,11,20,50 面值的硬币,问最少需要多少个硬币才能找出总值为 N个单位的零钱

解决这个问题其实也可以考虑使用贪心算法,每次使用面值最大的硬币,不足部分再用小额硬币补充。以兑换63元为例,可选择的硬币和方案为50+11+2。但是,使用贪心算法只能保证每一步取的是局部最优解,并不能保证最终结果是全局最优解。以兑换15元为例,贪心算法给出的组合方案为{11,1,1,1,1},但其实最优方案为{5,5,5}。

使用动态规划算法就能避免该问题。因为动态规划可以保证每次取到的子问题的解是最优解。

动态规划算法思路:

记d{n}={}表示兑换面值为n的最优解组合为{x1,x2,x3...}

从子问题出发,取0元只有一种方法为{0},即d(0)={0},取1元(0+1)元,最优解为1元+0元,即d(0)+1={0,1}={1};

取2元有两种方式,即d(0)+2={2}或d(1)+1={1,1},易知最化解为{2}......

以此累推,兑换面值为n的最优解为d(n)=max{d(n-i)+i},其中(i<=n)

硬币数量无限时,程序代码如下

import java.util.HashMap;
import java.util.Map;

public class CoinChange {  
	/**  
	 * @param coins  保存每一种硬币的币值的数组  
	 * @param money  需要找零的面值  
	 */ 
	public static void changeCoins(int[] coins,int money) {  
		int[] coinsUsed = new int[money + 1];  	 // 保存面值为i的纸币找零所需的最小硬币数  
		int valueKinds = coins.length;		//硬币种类数量
		coinsUsed[0] = 0;  //0元的最优解
		Map<Integer,HashMap<Integer,Integer>> coinChangeMap = new HashMap<Integer,HashMap<Integer,Integer>>();

		for (int cents = 1; cents <= money; cents++) {  
			// 当用最小币值的硬币找零时,所需硬币数量最多  
			int minCoins = cents;  
			HashMap<Integer,Integer> minCoinMap = new HashMap<Integer,Integer>();//保存各个面值的具体找零方案
			minCoinMap.put(1, cents);
			coinChangeMap.put(cents, minCoinMap);
			// 遍历每一种面值的硬币,看是否可作为找零的其中之一  
			for (int kind = 0; kind < valueKinds; kind++) {               
				int coinVal = coins[kind];
				int oppCoinVal = cents - coinVal;
				if (coins[kind] <= cents) {  // 若当前面值的硬币小于当前的cents则分解问题并查表  
					int tmpCount = coinsUsed[oppCoinVal] + 1;  
					if (tmpCount <= minCoins) {  
						HashMap<Integer,Integer> subMap = coinChangeMap.get(oppCoinVal);//子问题的最优解
						HashMap<Integer,Integer> tmpMap = new HashMap<Integer,Integer>();
						if(subMap != null){//要copy一份数据
							tmpMap.putAll(subMap);
						}
						if(tmpMap.containsKey(coins[kind])){//如果已经包含当前面值,则加一
							tmpMap.put(coins[kind], subMap.get(coins[kind])+1);
						}else{
							tmpMap.put(coins[kind], 1);
						}
						minCoins = tmpCount;  
						minCoinMap = tmpMap;
					}
				}  
			}  
			// 保存最小硬币数  
			coinsUsed[cents] = minCoins; 
			coinChangeMap.put(cents, minCoinMap);

			System.err.println("面值为 " + (cents) + " 的最小硬币数 : " 
					+ coinsUsed[cents]+",货币为"+ coinChangeMap.get(cents));  
		}  
	}  

	public static void main(String[] args) {  
		// 硬币面值预先已经按降序排列  
		int[] coinValue = new int[] { 50, 20, 11, 5, 2,1 };  
		// 需要找零的面值  
		int money = 23;  
		// 保存每一个面值找零所需的最小硬币数,0号单元舍弃不用,所以要多加1  
		changeCoins(coinValue,  money);  
	}  

}
程序运行结果如下

假设硬币的数量是有限的,都为2枚,则每个子问题在得到最优解的时候,应该再判断自己拥有的是否大于需要的,以及当前组合的价值是否刚好等于当前的子问题价值~!

硬币数量有限时,程序代码如下

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CoinChangeLimit {  
	/**  
	 * @param coins  保存每一种硬币的币值的数组  
	 * @param money  需要找零的面值  
	 */ 
	public static void changeCoins(int[] coins,  int money) {  

		Map<Integer,HashMap<Integer,Integer>> coinChangeMap = new HashMap<Integer,HashMap<Integer,Integer>>();
		Map<Integer,Integer> ownedMap = new HashMap<Integer,Integer>();	//拥有的硬币种类及数量
		int valueKinds = coins.length;
		for (int kind = 0; kind < valueKinds; kind++) {        
			ownedMap.put(coins[kind],2);
		}
		for (int cents = 1; cents <= money; cents++) {  
			// 当用最小币值的硬币找零时,所需硬币数量最多  
			int minCoins = cents;  
			HashMap<Integer,Integer> minCoinMap = new HashMap<Integer,Integer>();
			coinChangeMap.put(cents, minCoinMap);
			// 遍历每一种面值的硬币,看是否可作为找零的其中之一  
			for (int kind = 0; kind < valueKinds; kind++) {               
				// 若当前面值的硬币小于当前的cents则分解问题并查表  
				int coinVal = coins[kind];
				int oppCoinVal = cents - coinVal;
				if (coins[kind] <= cents) {  
					int tmpCount = getCoinCount(coinChangeMap.get(oppCoinVal))+1;  
					if (tmpCount <= minCoins) {  //要用等号
						HashMap<Integer,Integer> subMap = coinChangeMap.get(oppCoinVal);//子问题的最优解
						HashMap<Integer,Integer> tmpMap = new HashMap<Integer,Integer>();
						if(subMap != null){//要copy一份数据
							tmpMap.putAll(subMap);
						}
						if(tmpMap.containsKey(coins[kind])){//如果已经包含当前面值,则加一
							tmpMap.put(coins[kind], subMap.get(coins[kind])+1);
						}else{
							tmpMap.put(coins[kind], 1);
						}
						//确保拥有的数量大于等于结果数量且价值没有损失
						if(isMapCoverSubMap(ownedMap, tmpMap)
										&&	getCoinsValue(tmpMap) == cents){
							minCoinMap = tmpMap;
						}
					}
				}  
			}  
			// 保存最小硬币数  
			coinChangeMap.put(cents, minCoinMap);
			System.err.println("面值为 " + (cents) + " 的最小硬币数 : " 
					+getCoinCount(coinChangeMap.get(cents))+",货币为"+ coinChangeMap.get(cents));  
		}  
	}  
	
	/**
	 *  判断mapA是否完全覆盖mapB
	 *  如a为1=3,2=4  b为1=0,2=1,a完全覆盖b
	 *  如a为1=3,2=4  b为2=1,3=4,a没有覆盖b
	 */
	public static boolean isMapCoverSubMap(Map<Integer,Integer> A,Map<Integer,Integer> B){
		if(A == null) return false;
		if(B == null || B.size() <= 0) return true;

		Map.Entry<Integer, Integer> aEntry = null;
		Iterator<Map.Entry<Integer, Integer>> iter = B.entrySet().iterator();
		while(iter.hasNext()){
			aEntry = iter.next();
			int keyB = aEntry.getKey();
			if(!A.containsKey(keyB))  return false;
			if(A.get(keyB) < aEntry.getValue()) return false;
		}
		return true;
	}

	/**
	 *  返回当前硬币组合的总价值
	 */
	public static int  getCoinsValue(Map<Integer,Integer> coinMap){
		if(coinMap == null ) return 0;

		int sum = 0;
		Map.Entry<Integer, Integer> aEntry = null;
		Iterator<Map.Entry<Integer, Integer>> iter = coinMap.entrySet().iterator();
		while(iter.hasNext()){
			aEntry = iter.next();
			sum +=  aEntry.getKey() * aEntry.getValue();
		}

		return sum;
	}

	/**
	 *  返回当前硬币组合的硬币总数量
	 */
	public static int getCoinCount(Map<Integer,Integer> B){
		if(B == null ) return 0;

		int sum = 0;
		Map.Entry<Integer, Integer> aEntry = null;
		Iterator<Map.Entry<Integer, Integer>> iter = B.entrySet().iterator();
		while(iter.hasNext()){
			aEntry = iter.next();
			sum +=   aEntry.getValue();
		}

		return sum;
	}
	
	public static void main(String[] args) {  
		int[] coinValue = new int[] { 50, 20, 11, 5, 2,1 };  
		int money = 23;  
		changeCoins(coinValue,  money);  
	}  
}
程序运行结果如下





版权声明:本文为博主原创文章,未经博主允许不得转载。

找零钱-动态规划

问题:

动态规划 找零钱问题

这个用贪心的话 会出错 记录一下这个例子 比如金额是10块,面币为2 5 6 11 10 按照贪心的话 肯定选6和2张2,但是只要2张5块就可以了,因此贪心法会出现问题,所以,需要用动态规划,写出状态...

动态规划--换零钱

题目描述想兑换100元钱,有1,2,5,10四种钱,问总共有多少兑换方法递归解法#include using namespace std; const int N = 100; int dimes...

最少硬币找零问题-动态规划

动态规划把问题分为子为题

动态规划算法问题(经典找零案例)

问题: 给定数组arr,arr中的所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。 暴力搜索方法 思路分析...

贪心算法解决找零钱问题

   #include #include using namespace std; int arr[] = {100, 20, 10, 5, 1}; //零钱的种类 int N = siz...

动态规划系列(2)——找零钱问题

用动态规划算法来实现找零钱问题的求解。
  • dnxbjyj
  • dnxbjyj
  • 2017年04月18日 01:09
  • 527

找零钱问题

找零钱问题«问题描述设有n种不同面值的硬币,各硬币的面值存于数组T[1:n]中。现要用这些面值的硬币来找钱,可以实用的各种面值的硬币个数不限。当只用硬币面值T[1],T[2],…,T[i]时,可找出钱...
  • crcr
  • crcr
  • 2010年06月05日 22:25
  • 6046

动态规划算法思想解决找零钱问题

前言     关于找零钱问题,网上已经有很多相关的资料以及优秀的文章博客等。这里写这篇博客的初衷很简单,就是为了方便自己,回过头来捡起这个知识能快一点,接受起来更易理解点;他人的文章写的再好,毕竟是别...
  • niaonao
  • niaonao
  • 2017年10月16日 14:20
  • 379

动态规划--找零钱有多少种方法

问题: 给定数组arr,arr中的所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。 分析:arr长度为N,生...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:动态规划解决找零钱问题
举报原因:
原因补充:

(最多只允许输入30个字)