硬币找零的三种解答方案

设有N种面值的硬币,要求用最小的硬币数找M元零钱。这里给出三种解答:1、动态规划。2、贪婪、3、暴力搜索

1、动态规划

package my.dynamicprogram;

public class DynamicSolveConitsProblem {

	/**
	 * 只有三种硬币0是为了让数组在使用的时候下标从1开始算
	 */
	public static final int[] COINTS = new int[] {0, 1, 3, 5, 7, 13};
	/**
	 * 需要找零的钱数
	 */
	public static final int MONEY = 20;

	/**
	 * 构造最优解数据矩阵
	 * 
	 * @return
	 */
	public static int[][] initBestResult() {
		int[][] arr = new int[COINTS.length][MONEY + 1];
		// 如果硬币的面额是0元,则凑够i元,需要无穷多的硬币
		for (int i = 1; i <= MONEY; i++) {
			arr[0][i] = Integer.MAX_VALUE;
		}
		// 如果钱数是0,则凑够0元只需要0个硬币
		for (int i = 0; i < COINTS.length; i++) {
			arr[i][0] = 0;
		}

		for (int i = 1; i < COINTS.length; i++) {// 硬币面额数组,遍历
			for (int j = 1; j <= MONEY; j++) {
				if (j < COINTS[i]) {// 如果钱数j小于硬币COINTS[i]的面额,则就取硬币面额为COINTS[i-1],凑够j元的硬币个数,即arr[i][j] = arr[i-1][j];
					arr[i][j] = arr[i - 1][j];
				} else if (j == COINTS[i]) {// 如果钱数j刚好等于硬币COINTS[i]的面额,则
					arr[i][j] = 1;
				} else {// 如果硬币COINTS[i]的面额大于钱数j,则有两种处理方法。1、取硬币COINTS[i]。2、不取COINTS[i]。然后取结果较小的情况即可
					/**
					 * 1、如果取当前面额COINTS[i]的硬币,则当前已经有1个面额为COINTS[i]的硬币,那么要凑够j元还需要j-COINTS[i]元,
					 * 要凑够j-COINTS[i],在硬币数组COINTS的下标为i,钱数是j-COINTS[i]时的硬币个数arr[i][j-COINTS[i]] 此时硬币个数arr[i][j-COINTS[i]]+1.
					 * 2、如果不取当前面额COINTS[i]的硬币,则凑够j元,就需要arr[i-1][j]
					 */
					if (arr[i][j - COINTS[i]] + 1 < arr[i - 1][j]) {
						arr[i][j] = arr[i][j - COINTS[i]] + 1;
					} else {
						arr[i][j] = arr[i - 1][j];
					}

				}
			}
		}
		return arr;
	}

	/**
	 * 打印最优解数组矩阵
	 * 
	 * @param arr
	 */
	public static void printMatrix(int arr[][]) {
		System.out.print(String.format("%-5s", "") + " ");
		for (int i = 1; i <= MONEY; i++) {
			System.out.print(String.format("%-5s", String.valueOf(i)) + " ");
		}

		System.out.print("\n");
		for (int i = 1; i < COINTS.length; i++) {
			System.out.print(String.format("%-5s", COINTS[i]) + " ");
			for (int j = 1; j <= MONEY; j++) {
				System.out.print(String.format("%-5s", String.valueOf(arr[i][j])) + " ");
			}
			System.out.print("\n");
		}
	}

	/**
	 * 回溯最优解 按照构建矩阵的逻辑可知arr[i][j](i、硬币种类数组COINTS下标。j、钱数)取值有两种情况。
	 * 1、arr[i][j]=a[i-1][j]。此时并未选择第i种硬币
	 * 2、arr[i][j]=a[i][j-COUNTS[i]]+1。此时选择了第i种硬币 由此作为回溯的基本原理,实现结果如下:
	 * 
	 * @param arr
	 */
	public static void printBestPlan(int arr[][]) {
		String selectConintsTs = "";// 被选择的硬币的数组下标
		int cointsCnt = arr[COINTS.length - 1][MONEY];
		System.out.println("找"+MONEY+"元最少需要" + cointsCnt + "个硬币");
		int money = MONEY;
		for (int i = COINTS.length - 1; i > 0; i--) {
			while (arr[i - 1][money] != arr[i][money]) {
				selectConintsTs += i;
				money -= COINTS[i];
			}
		}
		String[] str = selectConintsTs.split("");
		System.out.println("选择的硬币为:");
		for (int i = 0; i < str.length; i++) {
			System.out.print(String.format("%-5s", COINTS[Integer.parseInt(str[i])] + "元"));
		}
	}

	public static void main(String ... args) {
		System.out.println("有面值1元、3元、5元、7元、13元五种硬币,求找20元零钱所使用的最小硬币数量。");
		int arr[][] = initBestResult();
		printMatrix(arr);
		printBestPlan(arr);

	}
}
2、贪婪

package my.dynamicprogram;

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

public class GreedySolveCointsProblem {

	/**
	 * 只有三种硬币0是为了让数组在使用的时候下标从1开始算
	 */
	public static final int[] COINTS = new int[] {1, 3, 5};

	public static Map<Integer, Integer> solve(int money) {
		Map<Integer, Integer> selectMap = new HashMap<Integer, Integer>();
		if (money < 1) {
			return selectMap;
		}

		for (int i = COINTS.length - 1; i >= 0; i--) {
			int currentCnt = money / COINTS[i];
			if (currentCnt > 0) {
				selectMap.put(COINTS[i], currentCnt);
			}
			money = money % COINTS[i];
			if (money == 0) {
				break;
			}
		}
		return selectMap;
	}

	public static void print(Map<Integer, Integer> result) {
		Iterator<Integer> key = result.keySet().iterator();
		int cnt = 0;
		System.out.println("选择硬币为:");
		while (key.hasNext()) {
			int tem = key.next();
			cnt += result.get(tem);
			System.out.println(tem + "元选择了" + result.get(tem) + "个    ");
		}
		System.out.print("\n总共选择了" + cnt + "个");
	}

	public static void main(String ... args) {
		System.out.println("有面值1元、3元、5元、7元、13元五种硬币,求找11元零钱所使用的最小硬币数量。");
		Map<Integer, Integer> selectMap = solve(11);
		print(selectMap);

	}

}
3、暴力搜索

package my.dynamicprogram;

/**
 * 
 * 如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?
 */
public class CointsProblem {

	public static int[] DEALUT_COINTS = {1, 3, 5};

	public static int solve(int money) {
		int[] arr = new int[money + 1];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = i;
		}
		if(money == 1||money ==2){
			return arr[money];
		}
		for (int i = 3; i <= money; i++) {
			for (int j = i - 1; j >= 0; j--) {
				for (int k = 0; k < DEALUT_COINTS.length; k++) {
					if (j + DEALUT_COINTS[k] == i && arr[j] + 1 < arr[i]) {
						arr[i] = arr[j] + 1;
					}
				}
			}
			System.out.println(i+"元需要"+arr[i]+"个硬币");
		}
		return arr[money];
	}
	
	public static void main(String ...args){
		solve(100);
	}
}
从文中可以看出,贪婪算法是最直接最简单的方式,也是我们常人容易接受和理解的。但是这里不能就此否定动态规划的作用,在此题中,我们的硬币只有一种属性,那就是面额,如果硬币有了其他的属性作为限制条件,比如重量作为限制条件(一个人负重有限,只能带走一定重量的硬币),那么贪婪算法就不再适用了。此类问题,典型的题目如01背包问题,背包容量是有限的,物品则有价值和重量两种属性,而一个人一次性只能带走固定重量的物品,那么在求一次性能带走的最大价值的物品的时候,就无法只用物品的一个纬度去计算了,比如按照每次拿的物品价值最大的唯独,或者每次拿物品重量最小的纬度,这样都不准确,此时动态规划算法的优势就体现出来了。

暴力搜索算法,则是最基础的算法,有利于去理解动态规划算法。也是我们平时在解决问题最容易想到的方案。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值