详解扔鸡蛋问题

887. 鸡蛋掉落

你将获得 K 个鸡蛋,并可以使用一栋从 1N 共有 N 层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

示例 1:

输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。

示例 2:

输入:K = 2, N = 6
输出:3

本题是谷歌用于面试的一道经典面试题之一。由于本题过于经典,谷歌公司已经不再将这题作为面试的候选题目了。

思路

假设\(N=100\)

  • 如果只有一个蛋 \(K=1\)

    只能从第一层开始,一层一层往上试,最差情况就要扔100

  • 无限个蛋 \(K=\infty\)

    使用二分查找

​ 第一个蛋从50楼开始扔,如果碎了,那么临界楼层就在0-50之间,否则就在50-100;

​ 第二个蛋在25层扔,碎了,在0-25之间,否则在25-50

​ 所以要扔的次数\(M\)满足 $2^M >= 100 , M>=6.64 $, 至少需要7

  • 两个蛋\(K=2\)

    试想一下,如果第一个蛋在某些情况下碎了,那就只剩下一个蛋,退化为第一种问题,只能一层一层往上试,所以第一个蛋的作用应该在与缩小范围,然后用第二个蛋试

    两个蛋\(A,B\)

    A:先在第10层扔,没碎就在20层扔,没碎在30层扔……。也就是依次在10,20,……,100层扔,A最多可以扔10次

    B:假如A已经确定了范围,在10层没碎,在20层碎了,那么就用B在10-20依次尝试。

    最坏情况下A在100层碎了,B在99层碎了,需要10+9

在刚才的情况中,每次扔鸡蛋的楼层都是等间隔的,B每次要扔的次数都是一样的,如果临界楼层比较靠后,A扔的次数就多了,如果让间隔变得不等,A每多扔一次,B的范围就缩小一次,这样总次数就可以平均下,也许会更好,我们尝试下

第一次在第n层扔,第二次加n-1层,第三次加n-2层……,也就是A每次扔的间隔都会缩小一,\(n,n-1,n-2,...\)\(1+2+3+……+n=n(n+1)/2>=100 ,n>=13.65\),取n=14

A: 14, 27,39, 50, 60, 69 ,77, 84, 90, 95, 99, 100

这种方法扔鸡蛋次数在12-14之间,最坏情况14

  • \(K\)个蛋,\(N\)层,最小移动次数\(M(K,N)\)

先从最简单情况说起,画一个表,3层楼,4个蛋

1 2 3 4
1 1 1 1 1
2 2
3 3

第一行,只有一层楼

第一列,只有一个蛋

假如第一个蛋在\(T\)层扔,碎,临界楼层在前面,不碎,在后面

最坏情况下要扔多少层:\(max\{M(K-1,T-1),M(K,N-T)\}+1 = M_T(K,N)\)

表示在第一个蛋扔到\(T\)层时,需要扔鸡蛋的个数,不要忘了加1(扔到第\(T\)层的一次操作)

问题来了,第一个\(T\)怎么确定?

最直接的方法就是遍历,以每层作为起始的第一个\(T\)

T 1 2 …… N
\(M_T\) \(M_1\) \(M_2\) …… \(M_N\)

第一个蛋可以扔在任意一层,在所有扔法中选最小值

\(M(K,N) = min\{M_1,M_2,……,M_N\}\)

利用动态规划填表就可以求解上述问题

代码

#动态规划
def eggdrop(K,N):
    #建表 n行 k列  
    dp = [[float('inf')]*(K+1) for _ in range(N+1)]
    
    #初始化 楼层为1 蛋为1
    for i in range(1,K+1):#0层 和 1层
        dp[0][i] = 0
        dp[1][i] = 1
    for i in range(1,N+1):#0个蛋 和 1个蛋
        dp[i][0] = 0
        dp[i][1] = i
        
    #填表 下面代码表示先填列
    for k in range(2,K+1):#不同蛋总数
        for n in range(2,N+1):#不同楼层总数
            for t in range(1,n):
                dp[n][k] = min(dp[n][k],max(dp[t-1][k-1],dp[n-t][k])+1)
    return dp[N][K]

复杂度

时间复杂度:\(O(N^2K)\),三层循环

空间复杂度:\(O(NK)\),表的大小

注意到上述选取\(T\)的过程:遍历每一个楼层,计算对应的值

\(M(K-1,T-1)\):随T增加而增加

\(M(K,N-T)\):随T减小而减小

使用二分查找

#二分查找
#动态规划
def eggdrop(K,N):
    #建表 n行 k列  
    dp = [[float('inf')]*(K+1) for _ in range(N+1)]
    
    #初始化 楼层为1 蛋为1
    for i in range(1,K+1):#0层 和 1层
        dp[0][i] = 0
        dp[1][i] = 1
    for i in range(1,N+1):#0个蛋 和 1个蛋
        dp[i][0] = 0
        dp[i][1] = i
        
    #求解
    for k in range(2,K+1):#不同蛋数结果
        for n in range(2,N+1):#不同楼层数
            left = 1;
            right = n;
            while (left < right):
                mid = left + (right - left+1) // 2;
                breakCount = dp[mid - 1][k - 1]
                notBreakCount = dp[n - mid][k]
                if (breakCount > notBreakCount):
                    right = mid - 1
                else:
                    left = mid;
                dp[n][k] = min(dp[n][k],max(dp[left-1][k-1],dp[n-left][k])+1)
    return dp[N][K]

复杂度

时间复杂度:\(O(KNlogN)\)

空间复杂度:\(O(NK)\),表的大小

references:
李永乐老师视频

posted @ 2020-09-06 14:02  鱼与鱼  阅读(980)  评论(0编辑  收藏  举报