挣扎于递归基础案例

时间:2017-03-10 18:34:17

标签: ruby recursion

我在以下算法上花了一些时间:

  

您将获得不同面额和总金额的硬币   金额。编写一个函数来计算最少数量的硬币   你需要弥补这个数额。如果那笔钱不能   由任何硬币组合组成,返回-1。

     

示例1:硬币= [1,2,5],金额= 11返回3(11 = 5 + 5 + 1)

     

示例2:coins = [2],amount = 3返回-1。

     

注意:您可以假设您拥有无限数量的每种   硬币。

这可能不是解决问题的最有效方法,但我想我可以通过尝试每个硬币并在每次尝试时启动新功能来解决它​​,其中新函数调用具有更新量。这将为每个硬币启动N个函数调用......但我稍后会处理它。

现在我处理以下问题:通常在进行递归调用时,我无法在基本情况下正确编码。例如,在这个问题中,如果金额的任何组合都无法弥补金额,我们必须返回-1。但是,我还需要计算最少数量的硬币。所以我想我拿一个min变量并调用1 + new_func_call。

然而,当这个new_func_call结束时没有运行时,它向上传递-1到递归调用堆栈,最终使得min为零。我不确定如何调整 - 我尝试以不同的方式改变我的代码,但也许我有一个概念问题。我知道它为什么会发生 - 只是不知道如何处理它。

Sample input:
Coins: [2]
Amount: 3

My output: 0
Correct output: -1 

代码:

def coin_change(coins, amount)
    coin_count(coins, amount, coins.min)
end

def coin_count(coins, amount, min_coin)
    with_coin = min = 1.0/0
    return 0 if amount == 0
    return -1 if amount < min_coin
    i = 0

    while i < coins.length
        with_coin = 1 + coin_count(coins, amount - coins[i], min) if amount - coins[i] >= 0
        min = [min, with_coin].min
        i += 1
    end

    min
end

4 个答案:

答案 0 :(得分:3)

  

现在我正在处理以下问题:通常在进行递归调用时,我无法在基本情况下正确编码。例如,在这个问题中,如果金额的任何组合都无法弥补金额,我们必须返回-1。但是,我还需要计算最少数量的硬币。

那么你有两个基本案例

  1. 如果amount状态变量为零,我们已完成计数,因此请返回count
  2. 如果我们的硬币耗尽(xs)并且仍未达到零 - amount,则返回-1
  3. 否则我们有两个递归案例

    1. oops:amount小于0,意味着减去的最后一枚硬币(x)太大了 - 不使用此硬币即可快退和递归
    2. 默认情况:向count添加1,从x中减去硬币(amount),并使用同一组硬币(xs

      这项工作的唯一要求是硬币首先按降序排序

      好的,所以所有这些都可以使用辅助助手(aux)在Ruby中轻松编码,以保存我们的状态变量。请记住使用count进行初始化,并确保xs按降序排序。 - 注意排序只发生一次 - 每次递归不会发生一次

      def fewest_coins amount, xs
        def aux count, amount, (x,*xs)
          if amount.zero?
            count
          elsif x.nil?
            -1
          elsif amount < 0
            aux (count - 1), (amount + x), xs
          else
            aux (count + 1), (amount - x), [x, *xs]
          end
        end
        aux 0, amount, xs.sort { |a,b| b <=> a }
      end
      
      fewest_coins 11, [1, 5, 2]         # => 3
      fewest_coins 2, [3]                # => -1
      fewest_coins 100, [1, 25, 10, 5]   # => 4
      

      检查您的理解

      作为练习,修改fewest_coins以输出构成答案的硬币数组

      # for example
      fewest_coins 199, [1, 5, 10, 25, 50]
      # => [50, 50, 50, 25, 10, 10, 1, 1, 1, 1]
      

答案 1 :(得分:2)

你可以这样做。

def count_ways(cents, coins)
  if coins.size == 1
    return (cents % coins.first) == 0 ? [cents/coins.first] : nil
  end 
  coin, *remaining_coins = coins
  (0..cents/coin).each_with_object([]) { |n, arr|
    count_ways(cents-n*coin, remaining_coins).each { |a| arr << [n, *a] } }
end 

def fewest(cents, coins)
  count_ways(cents, coins)&.map(&:sum)&.min
end

fewest(11, [5,2,1])
  #=> 3
fewest(199, [25,10,5,1])
  #=> 13 (Let me guess: 7 quarters, 2 dimes, 4 pennies) 
fewest(2, [3]) 
  #=> nil

require 'time'

t = Time.now
fewest(2835, [25,10,5,1])
  #=> 114 
Time.now - t
  #=> 7.6961 (seconds)

我从我的回答here中获取了count_ways

&后跟.的两个是Ruby的safe navigation operator,它是在Ruby v2.3.0中引入的。 Array#sum(和Enumerable#sum)首次出现在Ruby v2.4.0中。

答案 2 :(得分:1)

这个问题已经有了一个非常好的答案,可以向您展示如何解决您的问题,但我想指出您的算法实际发生了什么,所以您知道它为什么不适合您。

你的第一个问题是

coin_count(coins, amount - coins[i], min)

应该是

coin_count(coins, amount - coins[i], coins.min)

你没有传递最小的硬币,而是传递了你设定为Infinity的最小值,这使得该语句检查金额是否小于最小硬币:

return -1 if amount < min_coin

实际检查金额是否小于无穷大,这意味着您的coin_count总是返回-1。这导致了第二个问题:

1 + coin_count(coins, amount - coins[i], min)
#1 + -1 = 0

在递归编程中使用-1作为错误令人沮丧的是-1是一个有效的数字,并且经常导致逻辑问题。我会完全避免使用它,但如果你的提示或规格迫使你返回它,我只会在最后一秒使用它。尝试:

def coin_change(coins, amount)
    result = coin_count(coins, amount, coins.min)
    return -1 if result == 1/0.0
    return result
end

def coin_count(coins, amount, min_coin)
    with_coin = min = 1.0/0
    return 0 if amount == 0
    return 1/0.0 if amount < min_coin
    i = 0

    while i < coins.length
        with_coin = 1 + coin_count(coins, amount - coins[i], coins.min) if amount - coins[i] >= 0
        min = [min, with_coin].min
        i += 1
    end

    min
end

我将您的错误编号从-1更改为无穷大,这实际上使您的算法忽略了无效的排列,因为它们总是由.min()排序。在这种情况下,函数返回无穷大的唯一方法是返回的最小数字,只有在没有有效排列时才会发生。然后在fewest_coins中我将其设置为检查无穷大并返回-1。

哦,顺便说一句,有一种更简单的方法来循环遍历ruby中的东西:

coins.each do |coin|
  with_coin = 1 + coin_count(coins, amount - coin, coins.min) if amount - coin >= 0
  min = [min, with_coin].min
end

答案 3 :(得分:0)

这绝对不是最聪明的方法,它不是性能最好的方法,而且可能耗费大量时间,但这就是我在红宝石中做到的方式:

def get coins, amount
  coins = coins.sort
  max = amount / coins.first + 1
  (1..max).detect do |i|
    result = coins.repeated_permutation(i).detect do |e|
      e.reduce(:+) == amount
    end
    break result if result
  end || -1
end

get [1, 2, 5], 11
#⇒ [1, 5, 5]

get [2], 3
#⇒ -1