【问题标题】:How would you tweak my recursive Coin Sum algorithm solution?你会如何调整我的递归 Coin Sum 算法解决方案?
【发布时间】:2020-03-01 16:05:37
【问题描述】:

我正在处理一个硬币和问题:

给定一组可用面额和目标金额,我想获得总和达到该目标金额的组合数量。每个教派我都可以随心所欲。

示例:

输入:

  • 面额:[1, 5, 10]
  • 目标数量:10

预期输出:4

说明:我可以对相应的面额进行以下计数:

[10, 0, 0], [5, 1, 0], [0, 2, 0] 或 [0, 0, 1]

我的代码

function func(denomenations, targetSum) {
  let totalCombos = 0;
  function generate(denomsLeft, remainder) {
    if(remainder === 0){
      totalCombos += 1;
      return
    }
    if(remainder - denomsLeft[0] >= 0) {
      generate(denomsLeft, remainder - denomsLeft[0])
    }
    if(remainder - denomsLeft[1] >= 0) {
      denomsLeft.splice(0,1)
      generate(denomsLeft, remainder - denomsLeft[0])
    }
  }
  generate(denomenations, targetSum)
  return totalCombos
}

问题

我确定我的解决方案非常接近,但有点不对劲。

调用 func([1,5,10],10) 返回 3 而不是 4。我似乎没有得到 5 & 5 = 10 的硬币组合。

错误在哪里,我该如何解决?

【问题讨论】:

  • 欢迎来到Stack Overflow!很不清楚你在问什么——为什么函数应该返回 4 或任何东西?它应该做什么?请edit澄清。 (PS,如果您要计算总和为目标的组合,我只会将有效组合生成为列表,在 sum == 目标上过滤它们,然后返回该列表的长度)
  • .splice 改变数组。我宁愿在那里做denomsLeft = denomsLeft.slice(1);
  • @d.ben 至少对我来说很清楚......而且您提出的解决方案不适用于无限数量的可能组合。 OP 提出的解决方案效率更高。
  • @D.BenKnoble 这个算法是一个经典的组合问题,我需要找出在给定一定数量的面额的情况下,你可以用多少种方法来换取一定数量的钱。在我的示例中,我正在寻找添加到 10 个给定面额 [1,5,10] 的组合。这里的答案是 4 种组合,但是我的实现只返回 3 种组合
  • ....您尝试调试什么问题?

标签: javascript algorithm debugging recursion combinations


【解决方案1】:

你的代码有一些错误:

  • if(remainder - denomsLeft[1] >= 0) { 应该有 [0] 而不是 [1]

  • 您不应将 denomsLeftsplice 进行变异,因为它会影响其余递归搜索的结果(也在回溯之后)。相反,创建一个没有第一个元素的副本,然后传递它。

  • 另外,当您决定不再使用第一个面额时,您不应该从remainder 中扣除它。因此,将这一点与上一点结合起来,您应该这样做:

    generate(denomsLeft.slice(1), remainder)
    
  • 您还需要检查是否还有面额:

    if (!denomsLeft.length) return;
    

这将解决它。但我也建议:

  • >= 条件放在函数的开头(相反,以退出)。
  • 使用其他名称而不是无意义的func
  • 语句始终以分号结束,因为您不想让解析器来决定。

所以这将是结果代码:

function func(denomenations, targetSum) {
  let totalCombos = 0;
  function generate(denomsLeft, remainder) {
    if (remainder < 0) return;
    if (remainder === 0){
      totalCombos += 1;
      return
    }
    if (!denomsLeft.length) return;
    generate(denomsLeft, remainder - denomsLeft[0])
    generate(denomsLeft.slice(1), remainder)
  }
  generate(denomenations, targetSum)
  return totalCombos
}

console.log(func([1,5,10],10)); // 4
console.log(func([1,2,5],5)); // 4

【讨论】:

  • 关于 generate(denomsLeft.slice(1), remaining - denomsLeft[0]),确实将 denomsLeft.slice(1) 作为参数传递,在函数之前重新分配 denomsLeft 变量的值被评估?如果是这样,这意味着第二个参数 - 余数 - denomsLeft[0] - 使用已经“切片”的数组进行评估。对吗?
  • 另外,调用 coinSumCombis( [1, 2, 5], 5) 返回 6,但应该返回 4
  • 关于您的最后一条评论:确实,我更新了我的答案来解决这个问题。关于您的第一句话:每个函数执行都有自己的 denomsLeft 变量。通过使用denomsLeft.slice(1) 调用它,您不会更改当前执行的变量,但您可以确定递归函数执行上下文中的变量。
  • 这回答了问题吗?
  • 做得很好,是的 - 这确实有道理。我唯一坚持的是为什么我不能从余数中减去 denomsLeft[1] - 即 generate(denomsLeft.slice(1), 余数 - denomsLeft[0]) - 但我发现了一个非常有用的和相关视频,所以我会继续研究它
【解决方案2】:

Trincot 向an excellent analysis 说明了您的代码出了什么问题以及如何修复它。

不过,我想指出,有比问题中的方法和 trincot 的清理方法更简单的技术。

我可以这样做:

const coinCount = (denoms, target) => 
  target == 0 
    ? 1
  : denoms.length == 0 || target < 0
    ? 0
  : // else 
    coinCount (denoms, target - denoms[0]) + coinCount (denoms.slice (1), target)


console .log (coinCount ([1, 5, 10], 10))
console .log (coinCount ([1, 5, 10, 25], 100))

我们测试目标是否为0。如果是,我们返回1。如果这看起来令人惊讶,让我们记住一种方法可以使总和为零:我们根本不返回任何硬币!

如果目标不是零并且我们没有剩余面额,或者如果我们的目标是负数,则没有可用的硬币组合,我们返回0

最后,我们可以做出选择。我们要么使用第一面额的硬币,要么不使用。如果我们这样做,那么我们会使用相同的面额集合,并且总价值减去该面额的价值。如果我们不这样做,那么我们以后就不能使用它,所以我们用剩余的面额和当前的总数重复。将这两个选项返回的计数相加得到我们的总数。

我们可以看到递归是合理的,因为每个递归步骤要么减少了我们正在搜索的总数(因此我们最终会找到一个不大于零的数字),要么它减少了面额的数量(因此我们' 最终会达到零。)无论哪种方式,我们最终都会达到我们的基本情况之一。

使用解构,我们可能会选择一个变体:

const coinCount = ([denom = undefined, ...denoms], target) => 
  target == 0 
    ? 1
  : denom == undefined || target < 0
    ? 0
  : coinCount ([denom, ...denoms], target - denom) + coinCount (denoms, target)

在某些方面我更喜欢这个变体,但其中任何一个似乎都更清楚地表达了问题中的基本递归。

我认为这种技术的任何一种变体都更简单,尤其是原始技术依赖于内部 generate 函数的副作用。这个版本是一个纯函数,只使用一个表达式。

【讨论】:

    猜你喜欢
    • 2019-09-29
    • 2015-10-25
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多