动态规划解决找零钱问题

原创 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);  
	}  
}
程序运行结果如下





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

相关文章推荐

动态规划--换零钱

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

找零钱-动态规划

问题:

JAVA动态规划(一)--最少硬币找零问题

问题:要找K云的零钱,零钱的种类已知,保存在数组coins[]中,要求:求出构成N所需的最少硬币的数量和零钱的具体数值。 分析:(1)贪心算法:,先从面额最大的硬币开始尝试,一直往下找,知道硬币总和...

有意思的位运算

笔试的时候遇到的几个比较有意思的关于位运算的题。   问题一 请编写一个算法,不用任何额外变量交换两个整数的值。 给定一个数组num,其中包含两个值,请不用任何额外变量交换这两个值,并...

硬币找零问题(动态规划求解)

如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元? (表面上这道题可以用贪心算法,但贪心算法无法保证可以求出解,比如1元换成2元的时候) 首先我们思考一个问题,如何用最少...

找零钱问题的贪心算法(java描述)

问题描述:当前有面值分别为2角5分,1角,5分,1分的硬币,请给出找n分钱的最佳方案(要求找出的硬币数目最少)问题分析:根据常识,我们到店里买东西找钱时,老板总是先给我们最大面值的,要是不够再找面值小...

动态规划 找零钱问题

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

贪心算法解决找零钱问题

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

【剑指offer】面试题 42:连续子数组的最大和

题目描述 输入一个整型数组,数组里有正数也有负数。数组中的一个或者连续多个整数组成一个子数组。 求所有子数组的和的最大值。要求时间复杂度为O(n)。 时间限制:1...

【剑指offer】面试题 40:最小的 k 个数

题目描述 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 时间限制:1秒 空间限制:32768K 热度指数:...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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