詳解扔雞蛋問題
887. 雞蛋掉落
你將獲得 K
個雞蛋,並可以使用一棟從 1
到 N
共有 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:
李永樂老師視訊