【问题标题】:Three egg proble​m三蛋问题
【发布时间】:2012-07-21 03:20:20
【问题描述】:

我只是在阅读The Two Egg Problem

两个鸡蛋问题

您将获得两个鸡蛋,并可以进入一座 100 层高的建筑物。两个鸡蛋是一样的。目的是找出鸡蛋从该楼层的窗户掉出时不会破裂的最高楼层。如果一个鸡蛋掉下来并且没有破裂,它就完好无损,可以再次掉下来。然而,一旦一个鸡蛋被打破,那就是那个鸡蛋。

如果一个鸡蛋从n 地板上掉下来时碎了,那么它也会从上面的任何地板上碎掉。如果一个鸡蛋在坠落中幸存下来,那么它会在任何比这更短的坠落中幸存下来。

问题是:你应该采取什么策略来最小化找到解决方案所需的鸡蛋滴数?。 (最坏的情况是什么?)

我一直跟着,直到“看,我可以做三个”部分。作者指出,在第一个鸡蛋破裂后,它会退化为 2-egg 问题,并且可以递归解决。

很好,但是当使用 3 个鸡蛋而不是 2 个(对于第一个鸡蛋)时,我们不想选择更大的步长吗?我们从哪一层扔第一个鸡蛋?

有 1 个鸡蛋,我们必须从 1 楼开始。
使用 2 个鸡蛋,我们求解 n(n+1)/2=k 并向上取整,其中 n 是起始楼层,k 是楼层数。
有了 3... 我无法想出一个公式。


再想一想,有 2 个鸡蛋,最大掉落数等于我们从第一个鸡蛋掉落的楼层数。例如,有 2 个鸡蛋和 100 层,解决方案是 14,这意味着我们从 14 层掉落第一个鸡蛋,如果它破裂,我们必须再掉落 13 次,从 1 到 13 层。

用 3 个鸡蛋,解决方案是 9(如图所示)。但是我们不想把第一个鸡蛋扔到 9 楼,我们可以把它扔得更高,因为我们不必在中间进行 1 秒的迭代。

如果我们再次从 14 楼扔出去,它坏了,那么我们就递归。 n(n+1)/2=k 其中k 现在是 13... 但是这给了我们 4.815,如果我们 ceil 和那个加上我们之前的下降,我们得到 6,这低于实际的解决方案,所以这里有问题...


【问题讨论】:

  • 您能添加文章的简短描述吗?至少是标题,而不是“这篇文章”……为了上下文和“我们为什么要费心看它”?
  • @RolazaroAzeveires 描述在我问题的底部,不是吗?
  • 也许吧。我们怎么知道?你开始离题“我可以做三个”!做什么?三什么? “作者指出,在第一个鸡蛋破裂后”。蛋?!你在推理过程中开始一个问题......
  • @RolazaroAzeveires 是的。这个问题已经 4 岁了 :-) 我批准了你的编辑。

标签: algorithm math


【解决方案1】:

您可以使用简单的动态规划解决方案。 n - 楼层数。 k - 鸡蛋的数量。 D[n,k] = 你回答(最小投掷次数)。 D[j,1] = n-1 对于每个 1

计算k>1的D[n,k]的主要思路:

D[n,k] = 最大值 1

【讨论】:

    【解决方案2】:

    如果我们再次从 14 楼扔出去,它坏了,那么我们就递归。 n(n+1)/2=k 其中 k 现在是 13...但是这给了我们 4.815,如果我们 ceil 和 that 并加上我们之前的 drop 我们得到 6,这低于实际的解决方案,所以这里是错了……

    如果它不坏怎么办?那么你有一个 86 层的三蛋问题,这可能比 100 层的问题少一滴解决。

    假设您从第 50 层掉落了第一个鸡蛋。如果它坏了,你有一个 49 层的两个鸡蛋问题,最多需要 10 次额外的下降。所以最坏的情况是 11 滴(因为如果它没有破裂,那么 50 层楼的三个鸡蛋问题最多需要额外滴 7 滴)。

    如果您选择 37th 楼进行第一次掉落,如果它破裂,则您遇到了 36 层的两个鸡蛋问题,最多需要 8 次额外掉落。如果它没有破裂,那么你还有一个 63 层的三蛋问题。你想用最多 8 滴解决这个问题,所以如果下一滴打破鸡蛋,剩下的双蛋问题应该最多 7 滴可以解决,因此你可以为第二滴选择的最高楼层是 @987654321 @,因为 28 层是最高的,你最多可以用 7 滴和两个鸡蛋解决。如果鸡蛋没有破,你就有了一个 34 层的三蛋问题,还剩 7 滴。如果鸡蛋破裂是 21 (6*7/2),你当然可以用剩下的 6 滴解决最高的,所以你可以选择地板66 + 21 + 1 = 88。如果鸡蛋不破,你还有 12 层楼,剩下 6 滴,这已经可以只用两个鸡蛋了。

    系统地,使用d drop 和e egg 可以解决的最高楼层数是

              / 1, if d == 1
    F(e,d) = |  d, if e == 1
              \ F(e-1,d-1) + 1 + F(e,d-1), if e > 1 and d > 1
    

    如果你只有一滴,你别无选择,只能选择你还不知道鸡蛋不会破的最低层。如果这打破了它,并且你尝试了更高的楼层,你不知道一楼打破了鸡蛋。

    如果您只有一个鸡蛋,则必须按顺序检查每个楼层,直到鸡蛋破裂或您的掉落物用完为止。

    否则,如果第一滴是从高于F(e-1,d-1) + 1 的楼层掉落的,那么如果鸡蛋破裂,您可能无法找到第一个破裂的楼层。如果第一滴是从较低的楼层,如果鸡蛋不破,你不能用d-1 滴到那么高,所以第一滴应该从地板F(e-1,d-1) + 1。如果它坏了,你可以用剩余的e-1鸡蛋和d-1假设来解决。如果没有,您可以用剩余的水滴和鸡蛋解决下一个F(e,d-1) 楼层。

    相反,要找到f 地板和e 鸡蛋可能需要多少滴,你必须找到

    D(e,f) = min { d | F(e,d) >= f }
    

    可以通过计算F(e,d)矩阵求得,也可以使用动态规划:

    如果您选择地板s作为第一滴,如果鸡蛋破裂,您需要最多D(e-1,s-1)滴来确定地板。如果鸡蛋没有破裂,您最多需要D(e,f-s) 滴来确定地板。 所以第一滴选择地板s的最坏情况是

    WC(s,e,f) = 1 + max { D(e-1,s-1), D(e,f-s) }
    

    最坏的情况是最好的

    D(e,f) = minimum { WC(s,e,f) | 1 <= s <= f }
    

    (当然是D(e,0) = 0)。

    【讨论】:

    • 有没有办法找出具体从哪些楼层下降?鉴于我掌握了所有信息,包括鸡蛋的数量、掉落的数量和楼层的数量。
    • @Jim F 数组和WC 是不同算法的一部分。此外,我们无法确定鸡蛋破裂的楼层,因此getFirstBreakingFloor 至少被误导性地命名(可能是getFirstDroppingFloor,以找到我们第一次掉鸡蛋的楼层?)。而且我不明白getMinimumEggDrops 是如何工作的。我会将其实现为for(int i = 1; i &lt;= floors; ++i) { if (getNumberFloors(eggs, i) &gt;= floors) return i; } throw new WTFException();(省略对参数和早期返回的完整性检查,例如floors &lt;= 0)。
    • 现在到WC 的算法。这也是为了找到我们可能需要找到第一个破地板(如果有的话)的掉落数量,但它也可以用来找出从哪个地板掉落第一个鸡蛋(可以有多个选项) ,如果getMinimumEggDrops 正常工作,您的getFirstBreakingFloor 会这样做(它将返回最低的起始楼层,从而导致最小的总掉落数)。让我们首先假设您想确定第一个鸡蛋从哪个楼层掉落(假设您有多个鸡蛋)并且您有一个正确的getMinimumEggDrops
    • (简称drops)。首先设置lo = 1; hi = f;s_min = s = (lo + hi)/2。然后让b = drops(e-1,s-1); nb = drops(e,f-s); wc = 1 + max(b,nb);。如果b == nb 您找到了最佳起始楼层。如果b &gt; nb,没有更高的起始楼层更好,所以设置hi = s-1;,如果b &lt; nb没有更低的起始楼层更好,所以设置lo = s+1。然后s = (lo + hi)/2; b = drops(e-1,s-1); nb = drops(e,f-s); wc1 = 1 + max(b,nb);,如果wc1 &lt; wc,则设置wc = wc1; s_min = s。重复直到b == nblo == hi(这两种情况都意味着你已经找到了一个最佳的起始楼层)。
    • 这就是二分搜索。当然,与往常一样,要小心边缘条件。用WC实现drops,多一层鸡蛋多一层的情况做缓存,零层有0,一层1,@ 987654372@ 一个鸡蛋(没有鸡蛋扔)。然后执行二进制WC-search(在drops内)以找到最佳起点,但不是像上面那样返回s_min,而是将wc存储在缓存中(对于给定的鸡蛋和地板)并返回@ 987654377@。这会得到一个递归记忆的drops
    【解决方案3】:

    这个问题可以通过以下 3 种方法(我知道)来解决:

    1. 动态编程
    2. 使用二叉搜索树的解决方案
    3. 通过获得可以测试或覆盖给定数量的鸡蛋和给定的滴数的最大楼层数的直接数学公式来解决

    我先定义一些符号如下:

    e = number of eggs
    f = number of floors in building
    n = number of egg drops 
    Fmax(e, n) = maximum number of floors that can be tested with e eggs and n drops
    

    动态规划方法的关键在于Fmax的以下递归公式:

    Fmax(e, n) = 1 + Fmax(e-1, n-1) + fmax(e, n-1)
    

    而获得Fmax的直接数学公式的关键在于以下Fmax的递归公式:

    Fmax(e, n) = { ∑Fmax(e-1,i) for i = 1 to n } - Fmax(e-1, n) + n 
    

    使用二叉搜索树 (BST) 的替代解决方案也可以解决此问题。为了便于分析,我们画出BST,稍作修改如下:

    1.    If egg breaks then child node is drawn on left down side
    2.    If egg does not break then child node is drawn straight down side
    

    如果我们用上述表示形式绘制 BST,那么 BST 的宽度(即 BST 中的垂直列数)代表鸡蛋的数量。

    任何具有f个节点的BST,用上述表示形式绘制并受到BST

    因此得到最优解相当于得到BST中节点的排列方式,其高度受约束为:BST的宽度

    有关以上 3 种方法的更多详细信息,请查看我的博客:3 approaches for solving generalized egg drop problem

    【讨论】:

    • 想出 Fmax(e, n) 的公式是一个数学挑战。
    • 其中 e 和 n 都是变量。
    【解决方案4】:

    这里的博客解释了 1000 层的 3 个鸡蛋的数学方法:

    https://sankalpiitr.wordpress.com/2012/03/02/the-2-eggs-problem-extended-to-3-eggs/

    根据这个博客公式将是

    P = N + ( N * ( N + 1 ) * ( N – 1 ) ) /6

    其中 P 是楼层数,N 是所需的最少下落次数。

    所以求解 P=100,我们得到 N 为 8.2

    【讨论】:

      【解决方案5】:

      这与我在 Egg Drop Printing Solutions 提供的答案相同。我在这里为任何想要查看整个决策树和推理的人提供它。

      # This uses dynamic programming to find the basic information.
      def optimal_solution(floors, eggs):
          # dp[drops][eggs] = max_floors
          dp = []
      
          # With no drops, we can do no floors
          dp.append([0 for x in range(eggs+1)])
      
          # With one drop and any eggs, we can do 1 floor
          one_drop = [1 for _ in range(eggs+1)]
          one_drop[0] = 0 # Except no eggs is still no floors
          dp.append(one_drop)
      
          # Keep adding drops until we have our answer
          # Note, in python array element -1 is shorthand for the end of the array.
          while dp[-1][eggs] < floors:
              # 0 floors for 0 eggs.  1 more for one egg
              next_drop = [0, dp[-1][1] + 1]
              for i in range(2, eggs+1): # Python for 2..eggs
                  # The best we can do is drop at floor dp[-1][i-1].
                  # If the egg breaks, we can find the answer using that solution.
                  # If the egg holds, we can find another dp[-1][i] floors.
                  next_drop.append(dp[-1][i] + dp[-1][i-1])
              dp.append(next_drop)
      
          return dp
      
      # This turns that optimal solution into a decision tree.    
      def dp_to_decision_tree(dp, floors, start_floor=None, eggs=None, drops=None):
          # Figure out defaults if needed.
          if start_floor is None:
              start_floor = 0
          if drops is None:
              drops = len(dp) - 1
          if eggs is None:
              eggs = len(dp[0]) - 1
      
          # Are we done?
          if floors == start_floor:
              return start_floor
          elif dp[drops][eggs] < floors - start_floor:
              return None
      
          # Do we need all of our drops?
          while floors - start_floor < dp[drops-1][eggs]:
              drops -= 1
      
          drop_at = start_floor + dp[drops-1][eggs-1]
          if eggs == 1:
              drop_at = start_floor + 1
          if floors < drop_at:
              drop_at = floors
          return [
              drop_at,
              dp_to_decision_tree(dp,  floors,     drop_at,   eggs, drops-1),
              dp_to_decision_tree(dp, drop_at-1, start_floor, eggs-1, drops-1),
              {'eggs': eggs, 'floor_range': (start_floor, floors)}
              ]
      
      # This prints the decision tree in a human readable format.
      def print_decision_tree(tree, indent="", label="start"):
          if tree is None:
              print(f"{indent}{label}: ?")
          elif isinstance(tree, int):
              print(f"{indent}{label}: {tree} found")
          else:
              print(f"{indent}{label}: {tree[0]} {tree[3]}")
              print_decision_tree(tree[1], indent + "    ", label="held")
              print_decision_tree(tree[2], indent + "    ", label="broke")
      
      # And this calls the previous functions.
      def print_optimal_decisions(floors, eggs):
          print_decision_tree(
              dp_to_decision_tree(
                  optimal_solution(floors, eggs), floors))
      
      # And now we can try it.
      print_optimal_decisions(36, 3)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-05-29
        • 2011-07-08
        • 1970-01-01
        • 1970-01-01
        • 2011-08-06
        • 2010-11-25
        • 2020-05-18
        • 2011-05-06
        相关资源
        最近更新 更多