【问题标题】:StackOverflowError for coin change in Scala?StackOverflowError 用于 Scala 中的硬币变化?
【发布时间】:2019-01-24 00:37:02
【问题描述】:

我正在为 Scala 中的Coin (change) problem 编写递归函数。

我的实现因 StackOverflowError 而中断,我不知道为什么会发生。

Exception in thread "main" java.lang.StackOverflowError
    at scala.collection.immutable.$colon$colon.tail(List.scala:358)
    at scala.collection.immutable.$colon$colon.tail(List.scala:356)
    at recfun.Main$.recurs$1(Main.scala:58) // repeat this line over and over

这是我的电话:

  println(countChange(20, List(1,5,10)))

这是我的定义:

def countChange(money: Int, coins: List[Int]): Int =  {

  def recurs(money: Int, coins: List[Int], combos: Int): Int = 
  {    
      if (coins.isEmpty)
          combos
      else if (money==0)
          combos + 1
      else
          recurs(money,coins.tail,combos+1) + recurs(money-coins.head,coins,combos+1)

  }
  recurs(money, coins, 0)
} 

编辑:我刚刚在混合中添加了 else if 语句:

else if(money<0)
    combos

它消除了错误,但我的输出是 1500 东西 :(我的逻辑有什么问题?

【问题讨论】:

  • 你第二次调用recurs (recurs(money-coins.head,coins,combos+1)) 引入了一个无限循环。

标签: scala


【解决方案1】:

the accepted answer 中的第一个解决方案有一个多余的最后一个参数as noted by Paaro,所以我想去掉它。第二种解决方案使用map,我想避免使用它,因为第 1 周或我假设您正在学习的 Scala 课程中尚未涵盖它。此外,正如作者正确指出的那样,第二种解决方案会慢得多,除非它使用一些记忆。最后,Paaro's solution 似乎有一个不必要的嵌套函数。

这就是我最终得到的结果:

def countChange(money: Int, coins: List[Int]): Int =
  if (money < 0)
    0
  else if (coins.isEmpty)
    if (money == 0) 1 else 0
  else
    countChange(money, coins.tail) + countChange(money - coins.head, coins)

如您所见,这里不需要大括号。

我想知道它是否可以进一步简化。

【讨论】:

    【解决方案2】:

    这是基于您的代码的正确解决方案:

    def countChange(money: Int, coins: List[Int]): Int = {
      def recurs(m: Int, cs: List[Int], cnt: Int): Int =
          if(m < 0) cnt  //Not a change, keep cnt
          else if(cs.isEmpty) {
            if(m == 0) cnt + 1 else cnt // plus cnt if find a change
          }
          else recurs(m, cs.tail, cnt) + recurs(m-cs.head, cs, cnt)
      recurs(money, coins, 0)
    }
    

    总之有一个简短的解决方案(但效率不高,你可以缓存中间结果以使其高效。)

    def countChange(m: Int, cs: List[Int]): Int = cs match {
      case Nil => if(m == 0) 1 else 0
      case c::rs => (0 to m/c) map (k => countChange(m-k*c,rs)) sum
    }
    

    【讨论】:

    • 你能解释一下这个解决方案,因为我从概念上看不懂吗?
    【解决方案3】:

    可以省略cnt参数,实际上是从不累加的。 recurs 函数总是返回 0 或 1,因此优化的算法是:

    def countChange(money: Int, coins: List[Int]): Int = {
      def recurs(m: Int, cs: List[Int]): Int =
          if(m < 0) 0  //Not a change, return 0
          else if(cs.isEmpty) {
            if(m == 0) 1 else 0 // 1 if change found, otherwise 0
          }
          else recurs(m, cs.tail) + recurs(m-cs.head, cs)
      if(money>0) recurs(money, coins) else 0
    }
    

    【讨论】:

      【解决方案4】:

      @Eastsun 解决方案很好,但是当 money=0 时它会失败,因为它返回 1 而不是 0,但您可以轻松修复它:

      def countChange(money: Int, coins: List[Int]): Int = {
        def recurs(m: Int, cs: List[Int], cnt: Int): Int =
            if(m < 0) cnt  //Not a change, keep cnt
            else if(cs.isEmpty) {
              if(m == 0) cnt + 1 else cnt // plus cnt if find a change
            }
            else recurs(m, cs.tail, cnt) + recurs(m-cs.head, cs, cnt)
        if(money>0) recurs(money, coins, 0) else 0
      }
      

      【讨论】:

      • 我认为当 money=0 时返回 1 而不是 0 是合理的,因为改变 0 的方法只有一种。想想 0!=1 和 0-combinations 是 1。
      • 嗯..在我的练习中,我被要求在那种情况下返回 0 :)
      【解决方案5】:

      这是一种DP方法,可以减少递归方法中的大量重新计算

      object DP {
        implicit val possibleCoins = List(1, 5, 10, 25, 100)
        import collection.mutable.Map
      
        def countChange(amount: Int)(implicit possibleCoins: List[Int]) = {
          val min = Map((1 to amount).map (_->Int.MaxValue): _*)
          min(0) = 0
          for {
            i <- 1 to amount
            coin <- possibleCoins
            if coin <= i && min(i - coin) + 1 < min(i)
          } min(i) = min(i-coin) + 1
          min(amount)
        }
      
        def main(args: Array[String]) = println(countChange(97))
      }
      

      算法见DP from novice to advanced

      【讨论】:

        【解决方案6】:

        来自https://github.com/pathikrit/scalgos/blob/9e99f73b4241f42cc40a1fd890e72dbeda2df54f/src/main/scala/com/github/pathikrit/scalgos/DynamicProgramming.scala#L44的想法

        case class Memo[K,I,O](f: I => O)(implicit i2k:I=>K ) extends (I => O) {
          import scala.collection.mutable.{Map => Dict}
          val cache = Dict.empty[K, O]
          override def apply(x: I) = cache getOrElseUpdate (x, f(x))
        }
        def coinchange(s: List[Int], t: Int) = {
          type DP = Memo[ (Int, Int), (List[Int], Int),Seq[Seq[Int]]]
          implicit def encode(key: (List[Int], Int)):(Int,Int) = (key._1.length, key._2)
        
          lazy val f: DP = Memo {
            case (Nil, 0) => Seq(Nil)
            case (Nil, _) => Nil
            case (_, x) if x< 0  => Nil
            case (a :: as, x) => f(a::as, x - a).map(_ :+ a) ++ f(as, x)
          }
        
          f(s, t)
        }
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-11
          • 2016-01-24
          • 1970-01-01
          • 2017-10-28
          • 2014-01-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多