js贪心算法入门级教程,钱币找零(零钱兑换)问题解析


在这里插入图片描述

一、贪心算法

贪心算法(Greedy Algorithm,又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。

(1)贪心算法一般按如下步骤进行:

  1. 建立数学模型来描述问题;
  2. 把求解的问题分成若干个子问题;
  3. 对每个子问题求解,得到子问题的局部最优解;
  4. 把子问题的解合成原来问题的一个解。

(2)利用贪心算法求解的问题应具备如下2个特征:

  • 贪心选择性质
    一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。这就是贪心选择性质(无后效性)。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心选择最终导致问题的整体最优解。

  • 最优子结构性质
    当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法求解的关键所在。在实际应用中,至于什么问题具有什么样的贪心选择性质是不确定的,需要具体问题具体分析。

(3)贪心算法也存在如下问题:

  • 不能保证解是最佳的,因为贪心算法总是从局部出发,并没从整体考虑。(动态规划是整体最优)
  • 贪心算法一般用来解决求最大或最小解。
  • 贪心算法只能确定某些问题的可行性范围。

二、钱币找零问题

假设你是一个商店老板,你需要给顾客找零 n 元钱,你手上钱的面值为:100元,50元,20元,10元,5元,2元,1元。
请问如何找零使得所需要的钱币数量最少?

例如:你需要找零126元,则所需钱币数量最少的方案为100元1张,20元1张,5元1张,1元1张。

题目分析

根据问题 “如何找零使得所需要的钱币数量最少?”,可知,这是一个每次选择都需要最优子结构问题,因为如果不是最优子结构,那找零所需的钱币数量便不是最少的。例如我需要找零101元,正常最优选择为100元1张,1元1张,这样找零所需钱币才是最少的2张,是最优子结构;如果你找零50元2张,1元1张,找零钱币为3张,并非最少的找零钱币数量,所以这种选择并不是最优选择。

101 = 100 + 1(最优子结构)             
101 = 50 + 50 + 1
101 = 50 + 20 + 20 + 10 + 1

由上面的分析可以得知,当前题目中是含有最优子结构,也就是具有最优子结构性质;而我们可以每次都选择最优子结构来进行问题的求解,这就是贪心选择性质

来看代码设计

/**
 * @param {number[]} coins 零钱
 * @param {number}   n     当前金额
 * @return {number[]}
 */
function giveChange(coins, n){
	if(n === 0) return 0;							// 不给钱就不用找零钱了
	coins = coins.sort((a, b) => b - a); 			// 排列成正确的顺序,大-->小
	let length = coins.length;  		 			// 获取钱币的数量
	let counts= new Array(length);  	 			// 存储结果的指定长度的数组,每项表示对应面值的张数
	for(let i = 0; i < length; i++){
		if (n >= coins[i]){  						// 当前金额大于或者等于当前零钱金额
			let count = Math.floor(n / coins[i]);  	// 计算当前最大零钱金额所需张数,如126/100=1
			counts[i] = count;  					// 将所需张数放进结果数组的对应位置
			n = n - count * coins[i]; 			 	// 计算找零之后剩余需要零钱的金额,如126-100=26
		} else {
			counts[i] = 0;  			 			// 当前金额小于零钱金额则无法找零,计数为0
		}
	}
	return counts;
}
let result = giveChange([50, 5, 20, 10, 1, 100, 2], 126);
console.log(result); // [1, 0, 1, 1, 1]

三、钱币找零求钱币张数问题

假设现在有1元,2元,5元,10元,20元,50元,100元的纸币。现在要用这些钱来支付 k 元,至少需要用多少张纸币?

这个问题与上面第二个问题相似,只是这个求的是找零纸币的个数。利用贪心算法的思想,每次尽量选取最大面值纸币,计算每次所需要的纸币张数,然后累加即可。

/**
 * @param {number[]} coins 零钱
 * @param {number}   k     当前金额
 * @return {number}
 */
function coinChange(coins, k) {
	if(k === 0) return 0;							// 不给钱就不用找零钱了
	coins = coins.sort((a, b) => a - b);  			// 排列成正确的顺序,小-->大
    let length = coins.length;						// 纸币面额个数
    let sum = 0;									// 钱币张数,默认0张
    for (let i = length - 1; i >= 0; i--) {  		// 从最大的纸币面额开始找零(贪心)
        if (k >= coins[i]) {
            let count = Math.floor(k / coins[i]); 	// 计算当前最大零钱金额所需张数,如126/100=1
            sum += count;							// 累加所需纸币张数
            k = k - count * coins[i];				// 计算找零之后剩余需要零钱的金额,如126-100=26
        }
    }
    return sum;
}
let result2 = coinChange([1, 50, 2, 5, 20, 100, 10], 126);
console.log(result2);  // 4 

四、贪心算法与动态规划的区别

  • 在动态规划算法中,每步所作的选择往往依赖于相关子问题的解。因而只有在解出相关子问题后,才能作出选择。而在贪心算法中,仅在当前状态下作出最好选择,即局部最优选择。然后再去解做出这个选择后产生的相应的子问题。

  • 贪心算法所作的贪心选择可以依赖于以往所作过的选择,但决不依赖于将来所作的选择,也不依赖于子问题的解。

  • 动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式做出贪心选择,每一次贪心选择就将所求问题简化为一个规模更小的子问题。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值