【问题标题】:Choosing coins with least or no change given选择最少或没有零钱的硬币
【发布时间】:2015-11-30 05:37:26
【问题描述】:

我正在制作一个包含 10 美元、5 美元、3 美元和 1 美元硬币面额的游戏。玩家的库存中每种货币可能有 0 个或多个,总共最多 15 个硬币。我试图弄清楚如何正确选择硬币,以便获得最少的零钱作为回报。起初我认为这很容易解决,但现在我很难理解它。

这里有两个例子可以进一步解释这种情况:

示例 1:

用户携带这些硬币:5 美元、3 美元、3 美元、3 美元、1 美元、1 美元、1 美元、1 美元,并想以 12 美元的价格购买一件物品。解决方案是支付 5 美元、3 美元、3 美元、1 美元,并且不找零。

示例 2:

用户没有任何 1 美元硬币,并且携带 5 美元、3 美元、3 美元、3 美元、3 美元。一件商品以 12 美元的价格购买,因此他们支付 5 美元、3 美元、3 美元和 3 美元,并退还 2 美元的零钱。

由于我们首先选择较大的硬币,我不知道如何知道玩家的库存中是否有足够的低价值硬币(在本例中为 1 美元)以容纳示例 1,以及是否没有足以使用更多价值较高的硬币,如示例 2 所示。

在以下示例中可以看到另一个问题,尽管我很高兴让上述两个示例正常工作:

示例 3: 用户携带这些硬币:5 美元、3 美元、3 美元、3 美元。玩家以 6 美元买了东西。最好使用 $3 和 $3 并且不返回任何零钱,而不是使用 $5 和 $3 并给出 $2 零钱。

我相信前两个例子可以使用递归和贪心算法的变体来解决。

赏金奖:

我现在在下面添加了自己的答案作为临时解决方案。但是,我喜欢 Llama 先生下面的方法(请参阅他引用的链接),并希望找到一个 PHP 示例来满足这一点。我相信这种方法不需要递归并使用记忆。

如果有多种选择可以换取最少的零钱,那么我希望将领带发给用最少硬币支付的人。

【问题讨论】:

  • 我觉得只要用简单的动态规划就可以了
  • 好吧,我试图解决这个问题,因为它看起来很有趣。我写了一个适合你的算法,因为我不想设置 php 服务器,我是在 JS 中完成的。您可以将其转换为 php.ini 文件。让我知道它是否可以。我可以给你代码。
  • @HarryBomrah,是的,这绝对是一个很大的帮助。
  • 每个硬币的最大数量是多少,硬币的最大数量和/或硬币的最大总价值是多少?您可能会发现以某种方式存储计数而不是使用硬币列表有一些好处。 IE:[#10, #5, #3, #1] 所以你的例子看起来像:1) [0,1,3,4]2) [0,1,4,1]3) [0,1,3,0]
  • 我不确定你的更新,但我认为我的帖子回答了这个问题。你能确认或给我任何反馈吗?

标签: php algorithm


【解决方案1】:

问题可以定义为:

Return a subset of items where the sum is closest to x, but >= x.

这个问题称为子集和问题。它是NP完全的。你不会找到在伪多项式时间内运行的完美算法,只有不完美的启发式算法。

但是,如果硬币的数量非常少,那么彻底搜索解决方案空间肯定会起作用。

如果硬币的数量较大,那么您应该查看维基百科的概述:https://en.wikipedia.org/wiki/Subset_sum_problem#Polynomial_time_approximate_algorithm

【讨论】:

  • 一个完美的算法。这仅取决于您愿意花多少时间寻找目标。您可以使用一种天真的方法来尝试所有可能的硬币组合,这将被认为是“完美的”,因为它不会错过答案。但是,有更快的方法:stackoverflow.com/a/19572277/477563
  • 这是在现实生活中,我寻找最容易实现且相当有效的问题。 IIRC,“贪婪”算法(取最大的剩余账单
【解决方案2】:

我遇到了类似的问题,除了不能超过,组合必须保持在目标数量以下。最后,我使用了this answer 中介绍的动态方法。您应该也可以使用它。

它是这样的:

  1. 从一个包含单个空元素的列表开始。
  2. 对于列表中的每个条目...
    1. 复制条目并将其不包含的第一枚硬币(不是硬币价值!)添加到其中。
    2. 当且仅当列表中不存在新元素的新总和值时,才将新元素存储在原始列表中。
  3. 重复第 2 步,直到通过,其中没有新元素添加到列表中
  4. 迭代结果列表并保持最佳组合(使用您的标准)

*:我们可以做这个优化,因为我们并不特别关心组合中使用了哪些个硬币,只关心硬币集合的总和。

如果使用sum值作为key,上面的算法可以稍微优化一下。

【讨论】:

  • 谢谢。我发布了一个新的解决方案,我想出了一个可能的答案。它节省时间,但不能完全解决我的示例 3。
  • @kojow7 - 怎么样?在最后一步,第 4 部分,保留大于或等于您的目标的最低结果。您将每枚硬币视为一个独特的对象,是吗? (在步骤 2.1 中,如果不包括该特定硬币,则应添加它,而不是该硬币的 value
  • 抱歉,我的意思是我提出的解决方案不能解决我的示例 3。我仍在尝试完全了解您的解决方案将如何工作,并且正在考虑是否需要递归,那么它可能没有时间效率。
  • @kojow - 它根本不需要递归。最多需要 N 次迭代,其中 N 是您拥有的硬币数量。将您的硬币转换为一个对象,其中键是唯一 ID(序列号),值是硬币本身的值。这将使检查一组特定硬币的存在变得更加容易。
  • 在那个链接上,它说在最坏的情况下它会做 O(n^4),我假设我的最坏情况是 O(n^15)?
【解决方案3】:

我想出了以下解决方案。如果其他人可以为我批评它,我将不胜感激。

<?php

$coin_value = array(10,5,3,1);
$inventory = array(1,2,0,2);
$price = 17;

for ($i = 3; $i >= 0; $i--){

        $btotal = 0;
        $barray = array();

        for ($j = 0; $j < 4; $j++){
                $remaining = $price - $btotal;
                $to_add = floor($remaining / $coin_value[$j]);

                if ($i != 3 && $i == $j){
                        $to_add++;
                }

                if ($inventory[$j] < $to_add){
                        $to_add = $inventory[$j];
                }

                $btotal += $to_add * $coin_value[$j];

                for ($k = 0; $k < $to_add; $k++){
                        $barray[] = $coin_value[$j];
                }

                if ($btotal >= $price)
                        break 2; //warning: breaks out of outer loop

        }
}

$change_due = $btotal - $price;

print_r($barray);

echo "Change due: \$$change_due\n";

?>

它涵盖了我最初问题中的示例 1 和示例 2,但不涵盖示例 3。但是,我认为它现在可以使用,除非有人能提出更好的解决方案。我决定不使用递归,因为它似乎需要太多时间。

【讨论】:

    【解决方案4】:

    您可以使用堆栈来枚举有效组合。下面的版本使用了一个小的优化,计算是否需要最小的当前面额。如果有任何组合,则返回一个以上的最小变化组合,这可以通过记忆来限制;如果当前面额可以在零变化的情况下完成组合,也可以添加提前退出。我希望简洁注释的代码是不言自明的(如果您需要进一步解释,请告诉我):

    function leastChange($coin_value,$inventory,$price){
      $n = count($inventory);
      $have = 0;
      for ($i=0; $i<$n; $i++){
        $have += $inventory[$i] * $coin_value[$i];
      }
    
      $stack = [[0,$price,$have,[]]];
      $best = [-max($coin_value),[]];
    
      while (!empty($stack)){
    
        // each stack call traverses a different set of parameters
        $parameters = array_pop($stack);
        $i = $parameters[0];
        $owed = $parameters[1];
        $have = $parameters[2];
        $result = $parameters[3];
    
        // base case
        if ($owed <= 0){
          if ($owed > $best[0]){
            $best = [$owed,$result];
          } else if ($owed == $best[0]){
    
            // here you can add a test for a smaller number of coins
    
            $best[] = $result;
          }
          continue;
        }
    
        // skip if we have none of this coin
        if ($inventory[$i] == 0){
          $result[] = 0;
          $stack[] = [$i + 1,$owed,$have,$result];
          continue;
        }
    
        // minimum needed of this coin
        $need = $owed - $have + $inventory[$i] * $coin_value[$i];
    
        if ($need < 0){
          $min = 0;
        } else {
          $min = ceil($need / $coin_value[$i]);
        }
    
        // add to stack
        for ($j=$min; $j<=$inventory[$i]; $j++){
          $stack[] = [$i + 1,$owed - $j * $coin_value[$i],$have - $inventory[$i] * $coin_value[$i],array_merge($result,[$j])];
          if ($owed - $j * $coin_value[$i] < 0){
            break;
          }
        }
      }
    
      return $best;
    }
    

    输出:

    $coin_value = [10,5,3,1];
    $inventory = [0,1,3,4];
    $price = 12;
    
    echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,1,2,1],[0,1,1,4],[0,0,3,3]]
    
    $coin_value = [10,5,3,1];
    $inventory = [0,1,4,0];
    $price = 12;
    
    echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,0,4]]
    
    $coin_value = [10,5,3,1];
    $inventory = [0,1,3,0];
    $price = 6;
    
    echo json_encode(leastChange($coin_value,$inventory,$price)); // [0,[0,0,2]]
    
    $coin_value = [10,5,3,1];
    $inventory = [0,1,3,0];
    $price = 7;
    
    echo json_encode(leastChange($coin_value,$inventory,$price)); // [-1,[0,1,1]]
    

    更新:

    由于您也对最低数量的硬币感兴趣,我认为只有在我们能够保证不会跳过更好的可能性的情况下,记忆化才能发挥作用。我认为如果我们首先使用最大的硬币进行深度优先搜索,这是可以做到的。如果我们已经使用更大的硬币获得了相同的金额,那么继续当前线程是没有意义的。确保输入库存呈现按面额大小降序排列的硬币,并添加/更改以下内容:

    // maximum needed of this coin
    $max = min($inventory[$i],ceil($owed / $inventory[$i]));
    
    // add to stack 
    for ($j=$max; $j>=$min; $j--){
    

    【讨论】:

    • 如果有多个选项导致相同数量的找零,我怎样才能只返回使用最少硬币的那个?
    • @kojow7 感谢您的评论。你是在问我的代码吗?如果您查看注释为“基本情况”的部分,您会看到如果程序看到较低的更改组合,它将覆盖best 变量,但如果更改量相同,它将将该组合添加到列表。所以最终它会输出它找到的最低变化的尽可能多的组合。 (对于您的参数,我不担心速度;但无论如何,我在答案中包含了更优化解决方案的建议。)
    • @kojow7 程序通过找到小于或等于零的最高owed 参数来测试最小变化。 owed 参数显示在构建组合时还需要支付多少费用;一旦owed 小于零,就意味着你会得到零钱...
    • 是的,我问的是你的代码。我的意思是,如果有几个选项的变化量与上述第一种情况相同,您有 [$5, $3, $3, $1], [$5, $3, $1, $1, $1, $1] 和[$3, $3, $3, $1, $1, $1] 都是有效的选项,我希望它只返回 [$5, $3, $3, $1] 选项,因为它使用的硬币数量最少。谢谢。
    • @kojow7 我很高兴我的代码可以提供帮助。我的偏好是让你发布一个新的答案,而不是在这里修改我的代码。
    【解决方案5】:

    我能够做出的解决方案涵盖了您问题中发布的 3 个示例。并且总是用尽可能少的硬币来找零。

    我做的测试似乎执行得很快。

    我在这里贴出代码:

    <?php
    
    //Example values
    $coin_value = array(10,5,3,1);
    $inventory = array(5,4,3,0);
    $price = 29;
    
    //Initialize counters
    $btotal = 0;
    $barray = array(0,0,0,0);
    
    //Get the sum of coins
    $total_coins = array_sum($inventory);
    
    function check_availability($i) {
        global $inventory, $barray;
        $a = $inventory[$i];
        $b = $barray[$i];
        $the_diff = $a - $b;
        return $the_diff != 0;
    }
    
    /*
     * Checks the lower currency available
     * Returns index for arrays, or -1 if none available
     */
    function check_lower_available() {
        for ($i = 3; $i >= 0; $i--) {
            if (check_availability($i)) {
                return $i;
            }
        }
        return -1;
    }
    
    for($i=0;$i<4;$i++) {
        while(check_availability($i) && ($btotal + $coin_value[$i]) <= $price) {
            $btotal += $coin_value[$i];
            $barray[$i]++;
        }
    }
    
    if($price != $btotal) {
        $buf = check_lower_available();
        for ($i = $buf; $i >= 0; $i--) {
            if (check_availability($i) && ($btotal + $coin_value[$i]) > $price) {
                $btotal += $coin_value[$i];
                $barray[$i]++;
                break;
            }
        }
    }
    
    // Time to pay
    $bchange = 0;
    $barray_change = array(0,0,0,0);
    
    if ($price > $btotal) {
        echo "You have not enough money.";
    }
    else {
        $pay_msg = "You paid $".$btotal."\n\n";
        $pay_msg.= "You used ".$barray[0]." coins of $10\n";
        $pay_msg.= "You used ".$barray[1]." coins of $5\n";
        $pay_msg.= "You used ".$barray[2]." coins of $3\n";
        $pay_msg.= "You used ".$barray[3]." coins of $1\n\n\n";
        // Time to give change
        $the_diff = $btotal - $price;
        if (!empty($the_diff)) {
            for ($i = 0; $i < 4; $i++) {
                while($the_diff >= $coin_value[$i]) {
                    $bchange += $coin_value[$i];
                    $barray_change[$i]++;
                    $the_diff -= $coin_value[$i];
                }
            }
    
            $check_sum = array_sum($inventory) - array_sum($barray);
            $check_sum+= array_sum($barray_change);
            $msg = "";
            if ($check_sum < 15) {
                $change_msg = "Your change: $".$bchange."\n\n";
                $change_msg.= "You received ".$barray_change[0]." coins of $10\n";
                $change_msg.= "You received ".$barray_change[1]." coins of $5\n";
                $change_msg.= "You received ".$barray_change[2]." coins of $3\n";
                $change_msg.= "You received ".$barray_change[3]." coins of $1\n\n";
                $msg = $pay_msg.$change_msg;
            }
            else {
                $msg = "You have not enough space to hold the change.\n";
                $msg.= "Buy cancelled.\n";
            }
        }
        else {
            $msg = $pay_msg."You do not need change\n";
        }
        if ($check_sum < 15) {
            for ($i = 0; $i < 4; $i++) {
                $inventory[$i] -= $barray[$i];
                $total_coins-= $barray[$i];
            }
            for ($i = 0; $i < 4; $i++) {
                $inventory[$i] += $barray_change[$i];
                $total_coins+= $barray[$i];
            }
        }
        echo $msg;
        echo "Now you have:\n";
        echo $inventory[0]." coins of $10\n";
        echo $inventory[1]." coins of $5\n";
        echo $inventory[2]." coins of $3\n";
        echo $inventory[3]." coins of $1\n";
    }
    

    【讨论】:

    • 抱歉,这不适用于我的第三个示例。我也建议不要使用全局变量。
    【解决方案6】:

    我不懂 PHP,所以我在 Java 中尝试过。我希望这没关系,因为它的算法很重要。

    我的代码如下:

    package stackoverflow.changecalculator;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class ChangeCalculator
    {
        List<Integer> coinsInTil = new ArrayList<>();
    
        public void setCoinsInTil(List<Integer> coinsInTil)
        {
            this.coinsInTil = coinsInTil;
        }
    
        public Map<String, List> getPaymentDetailsFromCoinsAvailable(final int amountOwed, List<Integer> inPocketCoins)
        {
            List<Integer> paid = new ArrayList<>();
            int remaining = amountOwed;
    
            // Check starting with the largest coin.
            for (Integer coin : inPocketCoins)
                if (remaining > 0 && (remaining - coin) >= 0) {
                        paid.add(coin);
                        remaining = remaining - coin;
                }
    
            ProcessAlternative processAlternative = new ProcessAlternative(amountOwed, inPocketCoins, paid, remaining).invoke();
            paid = processAlternative.getPaid();
            remaining = processAlternative.getRemaining();
    
            removeUsedCoinsFromPocket(inPocketCoins, paid);
            int changeOwed = payTheRestWithNonExactAmount(inPocketCoins, paid, remaining);
            List<Integer> change = calculateChangeOwed(changeOwed);
    
            Map<String, List> result = new HashMap<>();
            result.put("paid", paid);
            result.put("change", change);
            return result;
        }
    
        private void removeUsedCoinsFromPocket(List<Integer> inPocketCoins, List<Integer> paid)
        {
            for (int i = 0; i < inPocketCoins.size(); i++) {
                Integer coin = inPocketCoins.get(i);
                if (paid.contains(coin))
                    inPocketCoins.remove(i);
            }
        }
    
        private List<Integer> calculateChangeOwed(int changeOwed)
        {
            List<Integer> change = new ArrayList<>();
            if (changeOwed < 0) {
                for (Integer coin : coinsInTil) {
                    if (coin + changeOwed == 0) {
                        change.add(coin);
                        changeOwed = changeOwed + coin;
                    }
                }
            }
            return change;
        }
    
        private int payTheRestWithNonExactAmount(List<Integer> inPocketCoins, List<Integer> paid, int remaining)
        {
            if (remaining > 0) {
                for (int coin : inPocketCoins) {
                    while (remaining > 0) {
                        paid.add(coin);
                        remaining = remaining - coin;
                    }
                }
            }
            return remaining;
        }
    }
    

    ProcessAlternative 类处理最大硬币不允许我们得到没有零钱返回的情况,因此我们尝试替代方案。

    package stackoverflow.changecalculator;
    
    import java.util.ArrayList;
    import java.util.List;
    
    // if any remaining, check if we can pay with smaller coins first.
    class ProcessAlternative
    {
        private int amountOwed;
        private List<Integer> inPocketCoins;
        private List<Integer> paid;
        private int remaining;
    
        public ProcessAlternative(int amountOwed, List<Integer> inPocketCoins, List<Integer> paid, int remaining)
        {
            this.amountOwed = amountOwed;
            this.inPocketCoins = inPocketCoins;
            this.paid = paid;
            this.remaining = remaining;
        }
    
        public List<Integer> getPaid()
        {
            return paid;
        }
    
        public int getRemaining()
        {
            return remaining;
        }
    
        public ProcessAlternative invoke()
        {
            List<Integer> alternative = new ArrayList<>();
            int altRemaining = amountOwed;
            if (remaining > 0) {
                for (Integer coin : inPocketCoins)
                    if (altRemaining > 0 && factorsOfAmountOwed(amountOwed).contains(coin)) {
                        alternative.add(coin);
                        altRemaining = altRemaining - coin;
                    }
                // if alternative doesn't require change, use it.
                if (altRemaining == 0) {
                    paid = alternative;
                    remaining = altRemaining;
                }
            }
            return this;
        }
    
        private ArrayList<Integer> factorsOfAmountOwed(int num)
        {
            ArrayList<Integer> aux = new ArrayList<>();
            for (int i = 1; i <= num / 2; i++)
                if ((num % i) == 0)
                    aux.add(i);
            return aux;
        }
    }
    

    我通过对示例 1 进行测试,然后针对示例 2 进行测试,最后转到示例 3。此处添加了过程替代位,原始测试硬币的替代返回 0 需要更改,因此我更新了输入到 15 而不是 12 的数量,因此它将计算所需的更改。

    测试如下:

    package stackoverflow.changecalculator;
    
    import org.junit.Before;
    import org.junit.Test;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertTrue;
    
    public class ChangeCalculatorTest
    {
        public static final int FIFTY_PENCE = 0;
        public static final int TWENTY_PENCE = 1;
        public static final int TEN_PENCE = 2;
        public static final int FIVE_PENCE = 3;
        public static final int TWO_PENCE = 4;
        public static final int PENNY = 5;
    
        public ChangeCalculator calculator;
    
        @Before
        public void setUp() throws Exception
        {
            calculator = new ChangeCalculator();
            List<Integer> inTil = new ArrayList<>();
            inTil.add(FIFTY_PENCE);
            inTil.add(TWENTY_PENCE);
            inTil.add(TEN_PENCE);
            inTil.add(FIVE_PENCE);
            inTil.add(TWO_PENCE);
            inTil.add(PENNY);
            calculator.setCoinsInTil(inTil);
        }
    
        @Test
        public void whenHaveExactAmount_thenNoChange() throws Exception
        {
            // $5, $3, $3, $3, $1, $1, $1, $1
            List<Integer> inPocket = new ArrayList<>();
            inPocket.add(5);
            inPocket.add(3);
            inPocket.add(3);
            inPocket.add(3);
            inPocket.add(1);
            inPocket.add(1);
            inPocket.add(1);
            inPocket.add(1);
    
            Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(12, inPocket);
    
            List change = result.get("change");
            assertTrue(change.size() == 0);
            List paid = result.get("paid");
            List<Integer> expected = new ArrayList<>();
            expected.add(5);
            expected.add(3);
            expected.add(3);
            expected.add(1);
            assertEquals(expected, paid);
        }
    
        @Test
        public void whenDoNotHaveExactAmount_thenChangeReturned() throws Exception {
            // $5, $3, $3, $3, $3
            List<Integer> inPocket = new ArrayList<>();
            inPocket.add(5);
            inPocket.add(3);
            inPocket.add(3);
            inPocket.add(3);
            inPocket.add(3);
    
            Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(15, inPocket);
    
            List change = result.get("change");
            Object actual = change.get(0);
            assertEquals(2, actual);
            List paid = result.get("paid");
            List<Integer> expected = new ArrayList<>();
            expected.add(5);
            expected.add(3);
            expected.add(3);
            expected.add(3);
            expected.add(3);
            assertEquals(expected, paid);
        }
    
        @Test
        public void whenWeHaveExactAmountButItDoesNotIncludeBiggestCoin_thenPayWithSmallerCoins() throws Exception {
            // $5, $3, $3, $3
            List<Integer> inPocket = new ArrayList<>();
            inPocket.add(5);
            inPocket.add(3);
            inPocket.add(3);
            inPocket.add(3);
    
            Map<String, List> result = calculator.getPaymentDetailsFromCoinsAvailable(6, inPocket);
    
            List change = result.get("change");
            assertTrue(change.size() == 0);
            List paid = result.get("paid");
            List<Integer> expected = new ArrayList<>();
            expected.add(3);
            expected.add(3);
            assertEquals(expected, paid);
        }
    }
    

    测试还不是最干净的,但到目前为止它们都通过了。稍后我可能会返回并添加更多测试用例,看看我是否可以破解它,但现在没有时间。

    【讨论】:

      【解决方案7】:

      此答案基于 גלעד-ברקן 的答案。我按照他的要求把它贴在这里。虽然没有一个答案是我正在寻找的答案,但我发现这是发布的最佳选择。这是我目前使用的修改后的算法:

      <?php
      
      function leastChange($inventory, $price){
      
          //NOTE: Hard coded these in the function for my purposes, but $coin value can be passed as a parameter for a more general-purpose algorithm
          $num_coin_types = 4;
          $coin_value = [10,5,3,1];
      
          $have = 0;
          for ($i=0; $i < $num_coin_types; $i++){
                  $have += $inventory[$i] * $coin_value[$i];
          }
      
          //NOTE: Check to see if you have enough money to make this purchase
          if ($price > $have){
                  $error = ["error", "Insufficient Funds"];
                  return $error;
          }
      
          $stack = [[0,$price,$have,[]]];
          $best = [-max($coin_value),[]];
      
          while (!empty($stack)){
      
                  // each stack call traverses a different set of parameters
                  $parameters = array_pop($stack);
      
                  $i = $parameters[0];
                  $owed = $parameters[1];
                  $have = $parameters[2];
                  $result = $parameters[3];
      
                  if ($owed <= 0){
                          //NOTE: check for new option with least change OR if same as previous option check which uses the least coins paid
                          if ($owed > $best[0] || ($owed == $best[0] && (array_sum($result) < array_sum($best[1])))){
      
                                  //NOTE: add extra zeros to end if needed
                                  while (count($result) < 4){
                                          $result[] = 0;
                                  }
                                  $best = [$owed,$result];
                          }
                          continue;
                  }
      
                  // skip if we have none of this coin
                  if ($inventory[$i] == 0){
                          $result[] = 0;
                          $stack[] = [$i + 1,$owed,$have,$result];
                          continue;
                  }
      
                  // minimum needed of this coin
                  $need = $owed - $have + $inventory[$i] * $coin_value[$i];
      
                  if ($need < 0){
                          $min = 0;
                  } else {
                          $min = ceil($need / $coin_value[$i]);
                  }
      
                  // add to stack
                  for ($j=$min; $j<=$inventory[$i]; $j++){
                          $stack[] = [$i + 1,$owed - $j * $coin_value[$i],$have - $inventory[$i] * $coin_value[$i],array_merge($result,[$j])];
                          if ($owed - $j * $coin_value[$i] < 0){
                                  break;
                          }
                  }
          }
      
          return $best;
      }
      

      这是我的测试代码:

      $start = microtime(true);
      
      $inventory = [0,1,3,4];
      $price = 12;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      
      
      $inventory = [0,1,4,0];
      $price = 12;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      $inventory = [0,1,4,0];
      $price = 6;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      
      $inventory = [0,1,4,0];
      $price = 7;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      
      $inventory = [1,3,3,10];
      $price=39;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      $inventory = [1,3,3,10];
      $price=45;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      //stress test
      $inventory = [25,25,25,1];
      $price=449;
      echo "\n";
      echo json_encode(leastChange($inventory,$price));
      echo "\n";
      
      $time_elapsed = microtime(true) - $start;
      echo "\n Time taken: $time_elapsed \n";
      

      结果:

      [0,[0,1,2,1]]
      
      [0,[0,0,4,0]]
      
      [0,[0,0,2,0]]
      
      [-1,[0,1,1,0]]
      
      [0,[1,3,3,5]]
      
      ["error","Insufficient Funds"]
      
      [-1,[25,25,25,0]]
      
      Time taken: 0.0046839714050293
      

      当然,这个时间是以微秒为单位的,因此它在几分之一秒内执行!

      【讨论】:

        【解决方案8】:

        这是我的解决方案,我不知道它的效率如何,但它确实有效,我愿意提供建议。

        <?php
        
            $player=array(0,3,1,0);//how much coins you have
            $player_copy=$player;
            $coin_count=array(0,0,0,0);//memorize which coins you gave
            $coin_value=array(1,3,5,10);
            $price=6;       //price of item
            $price_copy=$price;
            $z=3;
            $change=array(-1,-1,-1,-1,-1); //memorise possible changes you can get
            $k=0;
            $flag=0;
        
        label1: for($j=3;$j>=0;$j--){
                    $coin_count[$j]=0;
                    $player[$j]=$player_copy[$j];
                }
                for($j=$z;$j>=0;$j--){
                    while(($price>0) && 1<=$player[$j]){
                        $price-=$coin_value[$j];
                        $player[$j]--;
                        $coin_count[$j]++;
                    }
                }
                $change[$k++]=$price;
                 if($price!=0){
                        for($j=$z;$j>=0;$j--)
                            if($price_copy>$coin_value[$j]){
                                $z=$j-1;
                                $price=$price_copy;
                                goto label1;
                            }
                        $flag=1;
                 }
            //find minimum change 
                $minv=$change[0];
        
                 for($i=1;$change[$i]>=0 and $i<4;$i++)
                     if($change[$i]>$minv)
                        $minv=$change[$i];
                 $i;
          //when you find minimum change find which coins you have used
                 for($i=0;$i<4;$i++)
                     if($change[$i]==$minv && $flag==1){
                        $flag=2;
                        for($j=3;$j>=0;$j--){//reset coin_count and player budget
                            $coin_count[$j]=0;
                            $player[$j]=$player_copy[$j];
                        }
                         for($j=3-($i%2)-1;$j>=0;$j--){
                             while(($price>0) && 1<=$player[$j]){
                                 $price-=$coin_value[$j];
                                 $player[$j]--;
                                 $coin_count[$j]++;
                             }
                          }
                      }
        //prints result
                 for($j=0;$j<4;$j++)
                    printf("%d x %d\n",$coin_count[$j],$coin_value[$j]);
                 printf("change: %d\n",$minv);
        ?>
        

        【讨论】:

          猜你喜欢
          • 2020-05-09
          • 2018-11-05
          • 1970-01-01
          • 2019-07-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-09
          相关资源
          最近更新 更多