2个鸡蛋100层楼

本文深入探讨了经典的2个鸡蛋100层楼问题,利用动态规划方法寻找确定鸡蛋破碎临界楼层所需的最少实验次数。通过分析不同情况下的最优策略,得出实际的实验步骤,并提供了Python代码实现。

2个鸡蛋100层楼

问题

有2个鸡蛋和100层楼,鸡蛋从\(\ge x\)层落下会破,从\(\lt x\)层落下不破,问题:最坏情况下,确定\(x\)需要的最少实验次数。

分析

\(x\)可能的取值为\(1,2,3,...,99,100, \gt 100\)
若只有一个鸡蛋,显然\(x=100\)
现在有2个鸡蛋,考虑第一个鸡蛋在第一次实验中应该从哪层楼落下。

直观的想法是二分从50层落下。
但是若鸡蛋破了,接下来只剩余一个鸡蛋去测试1-49层楼,需要实验次数是49。
在此情况下,实验总次数是50。非常大。

那么第一个鸡蛋在第一次实验中到底应该从哪层楼落下?设在\(k\)层落下:

  • 若鸡蛋破了,则剩余1个鸡蛋,\(k-1\)层楼,实验次数是\(k-1\)
  • 若鸡蛋没破,则剩余2个鸡蛋,\(100-k\)层楼,实验次数未知;但当前这个问题和原题目就比较类似了;

动态规划

\(dp[100][2]\)表示100层楼2个鸡蛋所需要的实验次数;我们枚举第一个鸡蛋可能从哪层楼落下的,则有
\[ dp[100][2] = \min(\max(k-1, dp[100-k][2])) + 1, 1<=k<=100 \]

泛化后:

\(dp[i][j]\)表示\(i\)层楼\(j\)个鸡蛋所需要的实验次数;我们枚举当前鸡蛋可能从哪层楼落下的,则有
\[ dp[i][j] = \min(\max(dp[k-1][j-1], dp[i-k][j])) + 1, 1<=k<=i \]

观察

我们观察17层2个鸡蛋的结果,运行文末的代码,可以发现:

状态转移路径:

17 2 6 <- 15 2 5 <- 10 2 4 <- 6 2 3 <- 3 2 2 <- 1 2 1 <- 0 2 0

楼层集合答案
11
2, 322
4, 5, 6333
7, 8, 9, 104444
11, 12, 13, 14, 1555555
16, 17, …66

可以发现,对于状态转移而言,表格的每行都是由上一行末尾的楼层转移过来的;

实际实验策略:

2->7->11->14->16->17

第1次把鸡蛋1放在2层,若破了,则总计0+(2-2)=2次;若没破,则继续;
第2次把鸡蛋1放在7层,若破了,则总计1+(7-2)=6次;若没破,则继续;
第3次把鸡蛋1放在11层,若破了,则总计2+(11-7)=6次;若没破,则继续;
第4次把鸡蛋1放在14层,若破了,则总计3+(14-11)=6次;若没破,则继续;
第5次把鸡蛋1放在16层,若破了,则总计4+(16-14)=6次;若没破,则继续;
第6次把鸡蛋1放在17层,总计5+(17-16)=6次;

代码

#!/bin/usr/env python
# -*- coding: utf-8 -*-
__date__ = '1/16/19 15:26'


# i层j球
def solve(i, j):
    if dp[i][j] != -1:
        return dp[i][j]

    # 若一个球,则一层层试
    if j == 1:
        dp[i][j] = i
        path[i][j] = 0, 0
        return dp[i][j]

    # 若0层,则0次
    if i == 0:
        path[i][j] = 0, 0
        dp[i][j] = 0
        return dp[i][j]

    # max(第k层鸡蛋破了,第k层鸡蛋没破)

    mark_val = i + 1
    mark_path = 0, 0

    for k in xrange(1, i + 1):
        # 使用>而不是>= 意味着优先选择保留鸡蛋
        if solve(k-1, j-1) > solve(i-k, j):
            tmp_val = solve(k-1, j-1) + 1
            tmp_path = k-1, j-1
        else:
            tmp_val = solve(i-k, j) + 1
            tmp_path = i-k, j

        if mark_val > tmp_val:
            mark_val = tmp_val
            mark_path = tmp_path

    dp[i][j] = mark_val
    path[i][j] = mark_path
    # dp[i][j] = min([max(solve(k-1, j-1), solve(i-k, j)) for k in xrange(1, i+1)]) + 1
    return dp[i][j]


if __name__ == '__main__':

    dp = [[-1 for i in xrange(1000)] for j in xrange(1000)]
    path = [[(-1, -1) for i in xrange(1000)] for j in xrange(1000)]

    i, j = 17, 2
    solve(i, j)
    print [(k, dp[k][2]) for k in xrange(i+1)]

    print "状态转移路径: "
    print i, j, dp[i][j], '<-',
    method = [0]
    while path[i][j] != (0, 0):
        method.append(method[-1] + i - path[i][j][0])
        i, j = path[i][j]
        print i, j, dp[i][j], '<-',

    print
    print "实际实验策略: "
    print '->'.join([str(i) for i in method[1:]])

转载于:https://www.cnblogs.com/yuanquanxi/p/10279822.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值