钞票找零-贪心,动态规划算法

简介: 钞票找零-贪心,动态规划算法

钞票找零问题是一个非常古老的问题,百度那些都有,本文将一步步的讲解关于钞票找零的算法以及优化过程.

贪心算法

假设有1,2,5,10面值的钞票,现在需要找零89元,我们该怎么做呢?

解析一:

这里面,最简单的一种方法,也就是89/1=89 了,我们只需要89张1元面值的即可,

<?php
class Change
{
    protected $moneyArr = \[1, 2, 5, 10\];//零钱
    protected $changeMethod;//找零方法
    public function __construct($moneyArr = null)
    {
        $moneyArr == null || ($this->moneyArr = $moneyArr);
        sort($this->moneyArr);//顺序排序零钱
    }
    public function change($moneyNum)
    {
        $moneyArr = $this->moneyArr;
        $changeMethod = \[\];
        while ($faceValue = array_shift($moneyArr)) {//从小到大
            if ($faceValue <= $moneyNum) {//面值必须小于等于找零金额
                $quotient = floor($moneyNum / $faceValue);//做除数运算
                $moneyNum -= intval($quotient * $faceValue);//减去已经找过的
                if (isset($changeMethod\[$faceValue\])) {
                    $changeMethod\[$faceValue\] += $quotient;
                } else {
                    $changeMethod\[$faceValue\] = $quotient;
                }
            }
            if ($moneyNum == 0) {
                break;
            }
        }
        if ($moneyNum>0){
            return null;
        }
        $this->changeMethod = $changeMethod;
        return $this->changeMethod;
    }
}
$change = new Change(\[ 2, 5, 10\]);
var_dump($change->change(89));
//

这时候有个问题就是,都是按照最小面值找的,找零89块钱,竟然需要89张1元的纸币,那能不能在能找零的情况,尽可能的将找的纸币数量缩小呢?

这时候我们就需要用到贪心算法

贪心算法是指,在每一次情况下,都选择当前最优的解进行处理,

在这个场景里面,最优的解就应该是从大到小进行找零了,89块钱,先找最大面值的50块钱,然后找10块钱的,以此类推

<?php
class Change
{
    protected $moneyArr = \[1, 2, 5, 10\];//零钱
    protected $changeMethod;//找零方法
    public function __construct($moneyArr = null)
    {
        $moneyArr == null || ($this->moneyArr = $moneyArr);
        rsort($this->moneyArr);//倒序排序零钱
    }
    public function change($moneyNum)
    {
        $moneyArr = $this->moneyArr;
        $changeMethod = \[\];
        while ($faceValue = array_shift($moneyArr)) {//从小到大
            if ($faceValue <= $moneyNum) {//面值必须小于等于找零金额
                $quotient = floor($moneyNum / $faceValue);//做除数运算
                $moneyNum -= intval($quotient * $faceValue);//减去已经找过的
                if (isset($changeMethod\[$faceValue\])) {
                    $changeMethod\[$faceValue\] += $quotient;
                } else {
                    $changeMethod\[$faceValue\] = $quotient;
                }
            }
            if ($moneyNum == 0) {
                break;
            }
        }
        if ($moneyNum>0){
            return null;
        }
        $this->changeMethod = $changeMethod;
        return $this->changeMethod;
    }
}
$change = new Change(\[1, 2, 5, 10\]);
var_dump($change->change(89));
var_dump($change->change(89));
//array(3) {
  \[10\]=>
  float(8)
  \[5\]=>
  float(1)
  \[2\]=>
  float(2)
}

这样就可以在尽可能纸币数量小的时候找零了

动态规划

在上面的从大到小进行做除数运算,获得一个找零解之后,我们现在研究另一个问题:

当钞票金额只有3,5,需要找零11元时,你会发现上面的算法根本算不出结果,因为它不管从大到小进行除数找零,还是从小到大进行除数找零都不能找到结果(11-3*3=2,11-2*5=1),但其实11元是有解的(2*3+5)

这时候,我们需要在贪心算法的基础上,进行相应的规划(当每次找一个最优解时,尝试通过该解之后继续寻找,但是找零方法只记录到缓存中,如果尝试找零成功,则记录,尝试找零失败,则放弃这个最优解):

<?php
class Change
{
    protected $moneyArr = \[1, 2, 5, 10\];//零钱
    protected $changeMethod;//找零方法
    public function __construct($moneyArr = null)
    {
        $moneyArr == null || ($this->moneyArr = $moneyArr);
        rsort($this->moneyArr);//倒序排序零钱
    }
    public function change(int $moneyNum,?array $moneyArr=null)
    {
        if ($moneyArr===null){
            $moneyArr =$this->moneyArr;
        }
        $changeMethod = \[\];
        while ($faceValue = array_shift($moneyArr)) {//从大到小
            if ($faceValue <= $moneyNum) {//面值必须小于等于找零金额
                $quotient = floor($moneyNum / $faceValue);//做除数运算,这里有着最大面值的解,但是因为规划问题,如果$quotient过大可能会造成无解,所以当无解的时候,尝试降低数量
                for ($i = $quotient; $i > 0; $i--) {
                    $changeMethodTemp = \[\];//找零方法缓存
                    $moneyNumTemp=$moneyNum;//金额缓存
                    $moneyNumTemp -= intval($i * $faceValue);//减去已经找过的
                    if (isset($changeMethodTemp\[$faceValue\])) {
                        $changeMethodTemp\[$faceValue\] += $i;
                    } else {
                        $changeMethodTemp\[$faceValue\] = $i;
                    }
                    //如果当前没有找清,则尝试判断剩余情况是否能找清
                    if ($moneyNumTemp == 0){
                        $moneyNum = 0;
                        $changeMethod = $changeMethodTemp;
                        break 2;
                    }elseif($moneyNumTemp > 0) {
                        $changeMethodTemp2 = $this->change($moneyNumTemp,$moneyArr );
                        if ($changeMethodTemp2 === null) {
                            continue;
                        }
//                        有值代表能找清
                        $moneyNum = 0;
                        $changeMethod = $changeMethodTemp + $changeMethodTemp2;
                        break 2;
                    }
                }
            }
        }
        if ($moneyNum > 0) {
            return null;
        }
        $this->changeMethod = $changeMethod;
        return $this->changeMethod;
    }
}
$change = new Change(\[3, 5\]);
var_dump($change->change(11));

上面的算法步骤如下:

1:从大到小取出 面值

2:根据最大的做除数运算,获取到当前面值faceValue最多可以找quotient张,

3:根据quotient做循环操作,尝试quotient是否存在找零成功的解,如果存在则直接返回

4:如果quotient不存在找零成功,则继续尝试找quotient-1,以此类推,知道quotient等于0,则代表该面值不能找零成功

5:将面值改为第二大,继续2,3,4操作

加强版动态规划找最优解

通过上面的算法,我们实现了简单的动态规划

使其在面额为3,5,找零11元的情况下,被金额5"贪心迷惑",找2个金额5,导致算法无解

这个算法实现了在这种情况下,不贪心,不被眼前的2*5迷惑,为了"大局",舍弃了表面的最优

那么?这是最优解吗?

当面额只有1,30,50,找零90的情况下,根据贪心+规划算法,我们能得到50*1+30*1+1*10的情况,这需要用到12张钞票,但是实际情况我们只需要找30*3,3张钞票即可解决该问题.这代表着我们需要完全遍历所有能找零的方法,然后根据钞票张数不同进行选择最优解

<?php
class Change
{
    protected $moneyArr = \[1, 2, 5, 10\];//零钱
    protected $changeMethod;//找零方法
    public function __construct($moneyArr = null)
    {
        $moneyArr == null || ($this->moneyArr = $moneyArr);
        rsort($this->moneyArr);//倒序排序零钱
    }
    public function change(int $moneyNum,?array $moneyArr=null)
    {
        if ($moneyArr===null){
            $moneyArr =$this->moneyArr;
        }
        $optimalChangeMethod = \[\];//当前最优方法
        $optimalNum = -1;//当前最优张数
        while ($faceValue = array_shift($moneyArr)) {//从大到小
            if ($faceValue <= $moneyNum) {//面值必须小于等于找零金额
                $quotient = floor($moneyNum / $faceValue);//做除数运算,这里有着最大面值的解,但是因为规划问题,如果$quotient过大可能会造成无解,所以当无解的时候,尝试降低数量
                for ($i = $quotient; $i > 0; $i--) {
                    $changeMethodTemp = \[\];//找零方法缓存
                    $moneyNumTemp=$moneyNum;//金额缓存
                    $moneyNumTemp -= intval($i * $faceValue);//减去已经找过的
                    if (isset($changeMethodTemp\[$faceValue\])) {
                        $changeMethodTemp\[$faceValue\] += $i;
                    } else {
                        $changeMethodTemp\[$faceValue\] = $i;
                    }
                    //如果当前没有找清,则尝试判断剩余情况是否能找清
                    if ($moneyNumTemp == 0){//如果当前情况直接找清,则判断是否优于最优解
                        $banknoteNum = array_sum($changeMethodTemp);
                        if ($optimalNum==-1||$banknoteNum<$optimalNum){
                            $optimalNum = $banknoteNum;
                            $optimalChangeMethod = $changeMethodTemp;
                        }
                    }elseif($moneyNumTemp > 0) {
                        $changeMethodTemp2 = $this->change($moneyNumTemp,$moneyArr);
                        if ($changeMethodTemp2 === null) {
                            continue;
                        }
                        $banknoteNum = array\_sum($changeMethodTemp2)+ array\_sum($changeMethodTemp);
                        if ($optimalNum==-1||$banknoteNum<$optimalNum){
//                        有值代表能找清
                            $optimalNum =$banknoteNum;
                            $optimalChangeMethod = $changeMethodTemp + $changeMethodTemp2;
                        }
                    }
                }
            }
        }
        if ($optimalNum==-1){
            return null;
        }
        $this->changeMethod = $optimalChangeMethod;
        return $this->changeMethod;
    }
}
$change = new Change(\[3,5\]);
var_dump($change->change(11));
目录
相关文章
|
2月前
|
存储 算法
深入了解动态规划算法
深入了解动态规划算法
82 1
|
2月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
5月前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
78 8
|
5月前
|
算法 Python
算法不再难!Python分治法、贪心、动态规划实战解析,轻松应对各种算法挑战!
【7月更文挑战第8天】掌握Python算法三剑客:分治、贪心、动态规划。分治如归并排序,将大问题拆解递归解决;贪心策略在每步选最优解,如高效找零;动态规划利用子问题解,避免重复计算,解决最长公共子序列问题。实例展示,助你轻松驾驭算法!**
77 3
|
1月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
55 2
|
2月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
107 2
动态规划算法学习三:0-1背包问题
|
2月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
81 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
2月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
182 0
动态规划算法学习二:最长公共子序列
|
2月前
|
存储 人工智能 算法
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
|
2月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
177 0

热门文章

最新文章