Choosing coins with least or no change given

前端 未结 8 1382
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-02-07 04:00

I am making a game which consists of coin denominations of $10, $5, $3, and $1. The player may have 0 or more of each type of currency in his inventory with a maximum of 15 coin

8条回答
  •  被撕碎了的回忆
    2021-02-07 04:37

    You can use a stack to enumerate valid combinations. The version below uses a small optimization, calculating if a minimum of the current denomination is needed. More than one least change combinations are returned if there are any, which could be restricted with memoization; one could also add an early exit if the current denomination could complete the combination with zero change. I hope the laconically commented code is self-explanatory (let me know if you'd like further explanation):

    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;
    }
    

    Output:

    $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]]
    

    Update:

    Since you are also interested in the lowest number of coins, I think memoization could only work if we can guarantee that a better possibility won't be skipped. I think this can be done if we conduct our depth-first-search using the most large coins we can first. If we already achieved the same sum using larger coins, there's no point in continuing the current thread. Make sure the input inventory is presenting coins sorted in descending order of denomination size and add/change the following:

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

提交回复
热议问题