【问题标题】:Different ways to get balls from a box从盒子里拿球的不同方法
【发布时间】:2018-09-21 23:06:22
【问题描述】:

你有一个装有球的盒子,我们从盒子里取出所有的球
但是我们可以一次拉一个或一次拉三个
提取顺序很重要。
问题是有多少种不同的方法可以把球拉出来?
所以如果:
盒子里有 1 个球,只有 1 种方式。
盒子里有 2 个球,只有 1 种方式。
盒子里有 3 个球,有 2 种方式(一次拉 1 个或三个)
盒子包含 4 个球,有 3 种方式:
1111
13
31

给定的是 7 个球以 9 种不同的方式从盒子中取出球

所以问题是盒子里有多少球,

我想出的解决方案是递归的:

Int calculate(int balls){
   If(balls=0) return 0;
   If(balls=1) return 1;
   If(balls=2) return 1;
   If(balls=3) return 2;
    If(balls=4) return 3;

 return calculate(balls-1) + calculate(balls-3);
}

这是正确的吗?
有没有不使用递归的方法?

谢谢

【问题讨论】:

  • @user648026 递归是正确的。您可以为此研究 DP 解决方案。
  • oeis.org/A000930 包括各种计算方法。
  • 0球的答案不清楚;对我来说更自然的答案是 1,然后当 balls=3 时,您就可以开始使用递归公式了。

标签: algorithm


【解决方案1】:

您的解决方案是正确的。但是,有一些方法可以使用称为动态编程的技术来提高算法的性能。在这种情况下,您可以记忆结果,这意味着在使用递归计算每个中间结果后,将所有中间结果存储在查找表中。这允许通常需要指数时间才能在线性时间内完成的解决方案。这是 JavaScript 中的一个示例实现:

function calculate (balls, map = []) {
  if (balls in map) {
    return map[balls]
  }

  switch (balls) {
  case 0:
    return 0
  case 1:
    return 1
  case 2:
    return 1
  case 3:
    return 2
  default:
    return map[balls] = calculate(balls - 1, map) + calculate(balls - 3, map)
  }
}

console.time('dynamic')
console.log(calculate(50))
console.timeEnd('dynamic')

将其与朴素算法进行比较:

function calculate (balls) {
  switch (balls) {
  case 0:
    return 0
  case 1:
    return 1
  case 2:
    return 1
  case 3:
    return 2
  default:
    return calculate(balls - 1) + calculate(balls - 3)
  }
}

console.time('naive')
console.log(calculate(50))
console.timeEnd('naive')

【讨论】:

  • map[balls] !== undefined 而不是 balls in map。因为balls in map 本身看起来像 O(n)。
  • @vivek_23 不是。检查任何对象上的属性存在是 O(1) 平均,特别是在数组上是 O(1) 常数。
  • 感谢您的信息。那么,JS 可能会在后台进行未定义检查,使其成为 O(1)?
  • @vivek_23 map[balls] = undefined; (balls in map) === true 所以没有。这不是同一张支票。
  • @vivek_23 与map.hasOwnProperty('balls') 类似,不同之处在于它检查整个prototype 链而不是只检查自己的属性,而且它比使用JavaScript 实现它更优化。
【解决方案2】:

您不需要记忆(至少不是所有值)或解决递归来为此或类似情况编写非递归程序。

如下所示:

function calculate (balls) {
   if (balls=0) return 0; /* Or remove this line */
   if (balls<3) return 1;
   resMinus3=1;  /* The result for i-3 */
   resMinus2=1;  /* For i-2 */
   resMinus1=1;  /* And for i-1 */
   for(i=3;;++i) {
      newRes=resMinus1+resMinus3; /* The recursion formula */
      if (i>=balls) return newRes;
      resMinus3=resMinus2; /* Shifting results */
      resMinus2=resMinus1;
      resMinus1=newRes;
   }
}

原因是要计算球的值,您只需要球 1 和球 3 的值,因此您只需要跟踪三个先前的结果即可更新新的结果。或者,您可以将其写为矩阵更新:

[resMinus1;resMinus2;resMinus3] <-[0,1,0;0,0,1;1,0,1]*[resMinus1;resMinus2;resMinus3]

【讨论】:

  • 如果你使用矩阵求幂,你可以在 O(log n) 矩阵乘法中进行。
  • @rice 不错;并且由于矩阵仅包含非负整数,因此它应该在数值上是准确的。使用特征值分解可以得到一个接近 floor(1.46^(I-2)*1.31+0.5) 的公式,在 O(1) 中计算它;但是正确设置常量更复杂 - 并且对于一些小值可能会失败。
  • 其实有一个封闭的表格,我在OEIS/A000930中找到的。 "a(n) = floor(d*c^n + 1/2) 其中 c 是 x^3-x^2-1 的实根,d 是 31*x^3-31*x 的实根^2+9*x-1(c = 1.465571... = A092526 和 d = 0.611491991950812...)。”
【解决方案3】:

link in the comments,你可以找到这个等式:

a(n) = Sum_{i=0..floor(n/3)} 二项式(n-2*i, i)

function binom(n, k) {
  var coeff = 1;
  for (var i = n-k+1; i <= n; i++) coeff *= i;
  for (var i = 1;     i <= k; i++) coeff /= i;
  return coeff;
}
function calculate (balls) {
  sum = 0;
  for (i = 0; i <= Math.floor(balls/3); i++){
      sum += binom(balls - 2*i, i);
  }
  return sum;
  
}
console.time('someMathGenius')
console.log(calculate(50))
console.timeEnd('someMathGenius')

【讨论】:

  • "a(n) = floor(d*c^n + 1/2) 其中 c 是 x^3-x^2-1 的实根,d 是 31 的实根*x^3-31*x^2+9*x-1(c = 1.465571... = A092526 和 d = 0.611491991950812...)。” (Bertrand Cloitre,参见 OEIS/A0000930)
  • 后来我看到了。立即编辑!
  • @rici 是凭经验确定的还是以某种方式得出的?
  • @patrick:我不知道 M. Cloitre 是怎么想出来的,但它可以被证明。
  • @patrick:如果他的证明是正确的:-)。斐波那契数和其他类似序列也有类似的封闭形式解决方案。
【解决方案4】:

对于 N 个球,您可以拉出 0 到 floor(n/3) 个三倍。

对于 N 个球,你拉 k 个三分球,你也拉 N-3k 个单球。

现在问题被简化为计算您可以订购 k 件一种类型的东西和订购 N-3k 件另一种类型的东西的不同方式。这是选择(k + N-3k,k)=选择(N-2k,k)。

最终答案是从 k=0 到 floor(N/3) 的choose(N-2k,k) 的总和。

N=0: choose(0,0) = 1 so there is 1 way of choosing nothing.
N=1: choose(1,0) = 1
N=2: choose(2,0) = 1
N=3: choose(3,0) + choose(1,1) = 1+1 = 2
N=4: choose(4,0) + choose(2,1) = 1+2 = 3
...
N=7: choose(7,0) + choose(5,1) + choose(3,2) = 1 + 5 + 3 = 9

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-28
    • 2012-05-11
    • 1970-01-01
    相关资源
    最近更新 更多