了解鸡蛋掉落算法超过两个鸡蛋。

时间:2016-10-18 22:13:28

标签: algorithm optimization dynamic-programming code-analysis

http://datagenetics.com/blog/july22012/index.html

我正在使用上面提到的链接来了解eggdrop问题。我也看过网上的代码,我理解程度不错(递归有点令人困惑)但我似乎无法理解这个蛋掉落问题的一些主要内容。

假设我们有100个楼层

  • 对于2个鸡蛋,我们说我们从14开始,然后到14 +(14-1)。我明白为什么我们这样做是为了让更糟糕的案件时间保持一致。但是,我们从哪里开始吃三个鸡蛋?该公式表明,在最坏的情况下,3个鸡蛋最多可以尝试9次。显然我们不是从9开始,因为9 +(9 - 1)并不能让我们在100个时间内进行9次试验,那么我们从哪里开始3?不仅如此,我们如何解决这个问题?

  • 看起来对于3个鸡蛋,我们进行了几次试验,直到问题退化成2个鸡蛋和x个楼层。从概念上讲,这是有道理的,但我不明白如何形象化或实现这个

  • 我必须在最坏的情况下找到尝试的顺序并实现它,这就是我想要想象尝试的原因。

我希望这很清楚。这是我的第一个子弹,这是我的主要问题。如果我错过了任何信息,请告诉我,我会对其进行编辑。

4 个答案:

答案 0 :(得分:2)

等式n(n + 1)/ 2 = F只能用于求解2个蛋的最小最坏情况下的滴数。这也恰好是你可以放下第一个鸡蛋的地板之一,因为你提到的均匀性。你知道你可以从14楼掉下来,因为如果它破裂了你,最坏的情况下会有13个掉落。但是,如果它没有破裂,你上升到13层并且它会裂开,那么你最差的还有12个下降,而你的腰带已经下降了2个。通过这种模式,您可以看到通过从第n层下降蛋,然后是n +(n-1)层,然后是n +(n-1)+(n-2)层,最坏的情况是在每个阈值处保持不变。 / p>

无论你开始使用多少个鸡蛋,这都是你想要达到的目标,但找到一个最佳的楼层(n)使这个条件成立(实际上可以表示为@Amit指出的范围)可以&#39 ;用一个封闭的系列计算,就像2个鸡蛋一样。重要的是要注意,即使在(n + 1)n = F的情况下,n也只是可能的第一层值的许多答案之一。我们很方便地说这是 的答案,也许是粗心大意,因为我们可以用一个相对简单的系列证明它是真的。

因此,让我们使用更通用的方法来估计最小最坏情况下的滴数,然后看看我们知道哪些楼层可以实现。我们假设我们有函数g(floors, eggs),它返回特定楼层所需的最小最坏情况下的蛋滴。我们可以充满信心地说,如果eggs = 1,最糟糕的情况是我们将不得不从每个楼层放弃鸡蛋以找到阈值,因此g(floors, 1) = floors对于{{1}的任何值都是正确的}。我们也知道,如果我们有1层进行测试,那么总是只需要一个下降floors。除了这两种情况,我们知道以下内容:

g(1, eggs) = 1

为什么会这样?考虑到总楼层的数量,必须经过每一个楼层,看看最糟糕的情况是在每个楼梯上开裂鸡蛋。这是通过比较最坏情况(如果它是裂缝)或g(floors, eggs) = Min(n = 1->floors : 1 + max(g(n-1,e-1),g(floors-n, e)))与最坏情况(如果它没有裂缝)或g([current floor]-1, eggs-1)来完成的。

这两个值的最大值将是该特定楼层的最坏情况。如果我们跟踪每个最大值的全局最小值,我们将找到最坏情况下所需的最低值。发生这种情况的地板是放下鸡蛋的最佳地板。现在让我们在该函数中插入g(floors-[current floor], eggs)以更好地了解其工作原理,以及为什么我们也可以在开始时表示最小最坏情况数量的丢弃2个鸡蛋也是一系列的。

如果我们有2个鸡蛋,我们将始终将eggs = 2g([current floor]-1, 1)进行比较。这使得事情变得更容易,因为我们确切地知道如果我们在当前楼层破解鸡蛋的最坏情况:

g(floors-[current floor], 2) 注意 - 这里我们添加1,因为我们在测试下面的剩余楼层时已经完成了1次下降。

我们也知道,我们在每个楼层比较的两个函数(cf)在任意固定数量的总楼层F上都是两个不同方向的单调函数。这必须是真的,因为:

*worst case drops required* = g(cf-1, 1) + 1 = 1 + (cf-1)对于任何正面的cf和d,所以当你增加cf。

时,这个函数会增加

g(cf+d, 1) > g(cf, 1),因此当您增加cf。

时,此功能总是在减少

以上是很重要的,因为它让我们意识到这两个函数的最小最大值将发生在一个楼层(让我们称之为最佳楼层),它们返回彼此最接近的值,一个甚至敢说彼此相等!使用这个我们可以近似表示最小值发生在:

g( 1 -1,1)〜= 1 -1~ = g(F 1 ,2) 〜= 1+( 2 -1)〜= 1 + g( 1 - 2 的F-,2)〜= 2 +( 3 -1)〜= .....~ = 2 - >右边最远的值代表最坏的情况,如果鸡蛋没有在任何最佳阈值处破裂,那么我们将耗尽 2 数量的液滴来达到这一点,不计算第一滴。

1 的理论楼层是第一次下降可以发生以最小化最坏情况和 1 + 2 的地方必须发生第二次下降(假设 1 未能破解),一直到 1 + 2 ... + of n = F.让我们现在检查 1 2 之间的关系:

1 -1)= 1+( 2 -1),因此 2 = 1 < /子> -1

类似地

3 = 2 -1 = 1 -

最后我们可以说一般

n = 1

- (n-1)

我们知道如果我们已经经历了除最后一个之外的所有阈值楼层,并且没有人破坏了鸡蛋,那么我们就在 1 th drop。

1的 = 1 - ( 1 -1)= 1 =&gt;这个元素是我们系列中的最后一个

1 1 + n 的系列可以写成 1 +( 1 -1)+( 1 -2)+ .... + 1 = F我们知道可以表示为( 1 )( 1 +1)/ 2 = F.这使得找到最小最坏情况下的滴数是最佳下降的第一个鸡蛋是一种简单的运动,将F插入这个公式。

现在,当鸡蛋等于3时,让我们使用相同的功能!不幸的是,事实证明你在第一步就碰到了墙。还记得我们还在比较g(F-(cf+d),2) < g(F-cf,2)g([current floor]-1, eggs-1)吗?我们现在就说g(floors-[current floor], eggs),所以你要比较eggs = 3g(floors-[current floor], 3)

我们不能将这些函数中的任何一个简化为具有闭合形式解决方案的系列作为函数

g([current floor]-1, 2)

需要至少一个级别的递归来解决,所以无论我们将它减少到什么,总是会有一个带有g函数的术语。另一方面,如果我们试图利用任何

的事实

g(F - cf, 3)

存在g(f-1, 2) n,其中n是最小最差情况下的丢弃数。

如果我们将(n+1)n/2 = f-1重新排列为(n+1)n/2 = f-1

我们可能会尝试将g( 1 -1,2)设置为等于1 + g( 2 -1,2)来查找 2 作为 1 的函数,类似于我们在 1 时以 1 的形式发现的有2个鸡蛋。如果你记得我们把所有&#34;最佳&#34;滴水的地板,以第一滴的最佳地板表示,在一系列恰好具有封闭形式的解决方案。有3个鸡蛋,我们运气不好,因为这导致了一个“丑陋的”#34;没有递归就无法解决的系列。这与我们以2个鸡蛋开始的情况不同,因为我们能够将n = 1/2(sqrt(8f-7)-1) = g(f-1, 2)简化为g(cf-1, 1) + 1。这有助于我们构建所做的系列解决方案。

因此,没有漂亮的推导用于找到最佳的第一滴,就像2个鸡蛋的情况一样。下面我写了一个简短的算法,输出最小的最坏情况下降数和第一次下降的最佳下限(通常它可以多于一个,但我总是返回最后一个)。一旦你输入了2以外的值,你可以注意到它们不一定相等。

&#13;
&#13;
1 + (cf-1)
&#13;
&#13;
&#13;

答案 1 :(得分:1)

我们先来看几个例子:

如果你有1个鸡蛋,那么2层建筑需要多少次投掷? 3层楼? 4?
显然这很简单。你需要分别投掷2次,3次和4次,并且通常 n 投掷。

如果你有2个鸡蛋怎么办?两层多少次投掷? 3? 4?
2个楼层显然有2次投掷...... 3虽然很有趣。如果你从2 nd 楼层抛出一个鸡蛋并且它没有破裂,你可以从3 rd 楼层抛出它并且你知道答案(它会中断3 rd 楼层,或根本没有。如果第一个鸡蛋在2 nd 楼层抛出,你从另一个楼层抛出另一个鸡蛋,你又知道答案(它在1 st 楼层上破了这就是答案,或者它没有,答案是2 nd 。哈......所以3层楼只有2次投掷。但这不适用于4 th 层,我们还需要另外一次投掷。额外的投掷可以“买我们”不仅仅是一层楼。它实际上会让我们到6 th 楼层(我们很快就会看到它是如何工作的)。

事实证明,拥有更多鸡蛋对于6层楼或更低的建筑物没有任何影响。

假设我们知道我们可以用 m-1 鸡蛋和 n-1 投掷覆盖多少楼层,让我们称之为 h 。如果我们有 m 鸡蛋和 n 投掷,我们的最佳策略是通过 h + 1 th <的第一个蛋/ sub> floor - 这是我们可以选择的最高楼层。如果鸡蛋断了,我们有足够的鸡蛋( m-1 )和足够的鸡蛋( n-1 )来找到剩下的 h 地板。如果鸡蛋没有破裂,我们的下一步行动是上升到足够的楼层,以便我们被(m,n-1)覆盖并继续这样做,直到我们没有投掷为止。通过这种方式,我们将通过任何鸡蛋和投掷组合实现最大覆盖率。

这解释了我们的最佳策略,但我们还没有定义(m,n)覆盖的楼层数。这很简单:(m-1,n-1)将覆盖一些 h 楼层,(m,n)投掷本身将帐户 h + 1 楼层,其余投掷将允许额外的(m,n-1)楼层。 到目前为止,问题的建模应该非常清楚,我们可以定义一个简单的递归函数来计算最大覆盖高度:

function maxHeightByEggThrows(eggs, throws) {
  if(eggs === 0 || throws === 0)
    return 0;

  return maxHeightByEggThrows(eggs - 1, throws - 1) + 1 +
         maxHeightByEggThrows(eggs, throws - 1);
}

这完美无瑕,但这是一个糟糕的,无效的实现。我们来试试DP:

function maxHeightByEggThrowsDP(eggs, throws) {
  let eggThrows = [[]];

  for(let i = 0; i < throws; i++) {
    // A single egg can cover has many floors has throws are allowed
    eggThrows[0].push(i + 1);
  }
  for(let i = 1; i < eggs; i++) {
    // Any number of eggs can only cover 1 floor with a single throw
    eggThrows.push([1]);
  }

  for(let i = 1; i < throws; i++) {
    for(let j = 1; j < eggs; j++) {
      eggThrows[j][i] = eggThrows[j - 1][i - 1] + eggThrows[j][i - 1] + 1;
    }
  }

  return eggThrows[eggs - 1][throws - 1];
}

这看起来不太好,但出于性能原因它更好,而且,我们可以使用这样的实现来返回整个表并显示它:

function maxHeightByEggThrowsDP(eggs, throws) {
  let eggThrows = [[]];

  for (let i = 0; i < throws; i++) {
    // A single egg can cover has many floors has throws are allowed
    eggThrows[0].push(i + 1);
  }
  for (let i = 1; i < eggs; i++) {
    // Any number of eggs can only cover 1 floor with a single throw
    eggThrows.push([1]);
  }

  for (let i = 1; i < throws; i++) {
    for (let j = 1; j < eggs; j++) {
      eggThrows[j][i] = eggThrows[j - 1][i - 1] + eggThrows[j][i - 1] + 1;
    }
  }

  return eggThrows;
}

const eggs = 10;
const throws = 15;
let eggThrows = maxHeightByEggThrowsDP(eggs, throws);


// display our data (boilerplate code)
// add a "row header" so we can read the egg count
eggThrows.forEach((row, i) => row.unshift(i + 1));
d3.select('#eggThrows>tbody').selectAll('tr').data(eggThrows).enter().append('tr').selectAll('td').data(d => d).enter().append('td').text(t => t);

d3.select('#throwsHeader').attr('colSpan', throws);
#eggThrows > thead td {
  padding: 5px;
  background-color: #404040;
  color: white;
}

#eggThrows > tbody td {
  padding: 5px;
  background-color: #C0C0C0;
}
#eggThrows > tbody td:first-child {
  background-color: #C0FF30;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<table id="eggThrows"><thead><tr><td>Eggs</td><td id="throwsHeader">Throws</td></tr></thead><tbody></tbody></table>

所以我们知道最大高度,但我们想要第一个投掷楼......事实证明有多种选择。例如,我们现在可以看到,在经典的“2蛋14投掷”案例中,我们实际上可以获得105层楼的答案,而不仅仅是100.我们也知道2个鸡蛋和13个投掷覆盖91层,所以我们可以抛出来自9 th 楼层的第一个蛋,仍能在14次投掷中找到解决方案。

现在我们可以回答这个问题:

  

抛出的第一层不高于(maxHeightByEggThrows(m-1,n-1)+ 1)且不低于([建筑物高度] - maxHeightByEggThrows(m,n-1))
  (3个鸡蛋和100个楼层,这是在8 th 和37 th 楼层之间)

答案 2 :(得分:1)

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

  1. 动态编程
  2. 使用二进制搜索树的解决方案
  3. 通过获得最大楼层数的直接数学公式得出解决方案,该公式可以测试或覆盖给定数量的鸡蛋和给定的滴数
  4. 让我首先定义一些在之后进行的分析中使用的符号:

    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 or covered 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的宽度代表鸡蛋的数量。

    任何具有f个节点的BST,用上述类型的表示绘制并受到BST <= e(蛋的数量)的约束宽度是一种解决方案,但它可能不是最佳解决方案。

    因此,获得最优解决方案等同于获得BST中具有最小高度的节点的布置受到约束:BST的宽度<= e

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

答案 3 :(得分:0)

这是在swift中实现的算法(这包括以前所有算法的概念): -

//Here n is number of eggs and k is number of floors
func eggDrop(_ n: Int, _ k: Int) -> Int {
        var eggFloor: [[Int]] = Array(repeating:Array(repeating: 0, count: k+1),count: n+1)
        if k == 1 {
            return k
        }
        for i in 1...n {
            eggFloor[i][1] = 1
            eggFloor[i][0] = 0
        }
        for j in 1...k {
            eggFloor[1][j] = j
        }
        for i in 2...n {
            for j in 2...k {
                eggFloor[i][j] = Int(INT_MAX)
                for x in 1...j {
                    let attempts = 1 + max(eggFloor[i-1][x-1], eggFloor[i][j-x])
                    if attempts < eggFloor[i][j] {
                        eggFloor[i][j] = attempts
                    }
                }
            }
        }
        return eggFloor[n][k]
    }