1. 程式人生 > 實用技巧 >詳解扔雞蛋問題

詳解扔雞蛋問題

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:
李永樂老師視訊