1、題目:
2個雞蛋,從100層樓上往下扔,以此來測試雞蛋的硬度,比如雞蛋在第9層沒有摔碎而在第10層摔碎了,那麼雞蛋不會摔碎的零界點就是9層,如何用最少的嘗試次數,測試出雞蛋不會摔碎的臨界點?
2、解決思路
2.1、最笨法:
把其中一個雞蛋從第1層開始往下扔,如果第1層沒碎換到第2層扔,如果第2層沒碎換到第3層扔,,,如果第59層沒碎換到第60層扔,如果第60層碎了,說明不會摔碎的臨界點是59層,最壞情況下需要扔100次
2.2、二分法:
把雞蛋從50層往下扔,如果第一枚在50層碎了,就從第1層開始(一共只有兩個雞蛋,第一個雞蛋碎了,第二個雞蛋就只能用最保守的方式一層一層扔,假如第二個雞蛋從25層扔,碎了,就無法判斷臨界點了);如果沒碎,繼續使用二分法,在剩餘樓層的一半(75層)往下扔,,,,最壞需要嘗試50次
2.3、平方根法:
做一個平方運算,100的平方根是10,因此我們嘗試每10層扔一次,第一次從10層扔,第二次從20層扔,第三次從30層,,,一直到100層,最好情況是在第10層碎掉,嘗試次數爲1+9=10次;最壞情況是在第100層碎掉,嘗試次數爲10+9=19次
2.4、方程法:將思路逆轉
假設問題存在最優解,這個最優解的最壞情況嘗試次數是x次,那麼我們第一次扔雞蛋該選擇哪一層?
答案是從第x層開始,選擇更高一層或更第一層都不合適
分析:假設第一次扔在第x+1層,如果第一個雞蛋碎了,那麼第二個雞蛋只能從第1層一層一層扔,一直到第x層,這樣總共扔了x+1次,和假設嘗試x次相悖,由此可見,第一次扔的樓層必須小於x+1次
假設第一次扔在第x-1層,如果第一個雞蛋碎了,那麼第二個雞蛋開始一層一層扔,直到第x-2層,這樣我們總共扔了x-1次,雖然沒有超過假設次數,但似乎有些過於保守(什麼意思,還沒理解)
假設第一次扔在第x層,如果第一個雞蛋碎了,那麼第二個雞蛋開始一層一層扔,一直到第x-1層,共x次
因此要向儘量樓層跨度大一些,又保證不超過假設的嘗試次數x,那麼第一次扔雞蛋的最優選擇就是第x層
更進一步。。。
如果第一次扔雞蛋在第x層,但並沒有打碎,第二次扔雞蛋在哪一層?
如果第一次扔雞蛋沒碎,我們的嘗試次數消耗了一次,問題就轉爲,兩個雞蛋在100-x層樓往下扔,要求嘗試次數不得超過x-1次,則第二次嘗試的樓層跨度爲x-1層,絕對樓層是x+x-1層;如果還沒碎,第三層樓層跨度是x-2,第四層是x-3!
so,x+(x-1)+(x-2+...+1=100,左邊多項式是各次扔雞蛋的樓層跨度之和,假設嘗試x次,所以該多項式共有x項,右邊是總的樓層數100,解方程可得x=14((x+1)*x/2=100)
因此,最優解在最壞情況的嘗試次數是14次,第一次扔雞蛋的樓層也是14層,在第一個雞蛋沒碎的情況下,嘗試的樓層爲:14,27,39,50,60,69,77,84,90,95,99,100,假如雞蛋不會碎臨界點在65層,那麼依次:14,27,50,60,69碎了,讓後第二個雞蛋從61層開始61,62,63,64,65,66碎了,即不會碎的臨界點是65層
3、動態規劃解題:有M層樓N個雞蛋,找到雞蛋摔不碎的臨界點,需要嘗試幾次?
可以把m層樓和n個雞蛋的問題轉化爲一個函數F(m,n),其中樓層數m和雞蛋數n是函數的兩個參數,函數的值則是最優解的最大嘗試次數,假設第一個雞蛋扔出的位置在第x層(1<=x<=m),會出現兩種情況:
1).第一個雞蛋沒碎,則剩餘m-x層樓和n個雞蛋,F(m-x,n)+1
2).第一個雞蛋碎了,則從1到x-1層嘗試,剩餘雞蛋n-1,F(x-1,n-1)+1
so,我們要求m層樓n個雞蛋條件下,最大嘗試次數的最小解,即
F(m,n)= Min(Max(F(m-x,n)+1,F(x-1,n-1)+1)) . 1<=x<=m
3.1、eg:3個雞蛋,4層樓
首先填充第一個雞蛋在各個樓層的嘗試次數,以及任意多雞蛋在1層樓的嘗試次數,原因:只有一個雞蛋,so只能從1層扔到最後一層,嘗試次數爲樓層數量;只有一個樓層,無論有幾個雞蛋,也只有一種扔法,嘗試次數只能爲1
2個雞蛋2層樓,帶入狀態轉移方程式F(2,2)=Min( Max(F(2-x,2)+1,F(x-1,2-1)+1)) )
x可以爲1和2,so:
x=1時,F(2,2)=Max(F(2-1,2)+1,F(1-1,2-1)+1))=Max(F(1,2)+1, F(0,1)+1)=Max(1+1, 0+1)=2
x=2時, F(2,2)=Max(F(2-2,2)+1,F(2-1,2-1)+1))=Max(F(0,2)+1, F(1,1)+1)=Max(0+1, 1+1)=2
取最小值即爲2
依次類推
先上代碼(當然了,還是別人寫的,看的可真費勁,話說動態規劃看一次不懂一次。。。)
3.2、python版代碼解決floor層樓仍egg個雞蛋的問題
# 我們要求m層樓n個雞蛋條件下,最大嘗試次數的最小解,即
# F(m,n)= Min(Max(F(m-x,n)+1,F(x-1,n-1)+1)) . 1<=x<=m
import numpy as np
def get_min_floor(floors=100, eggs=2):
# 第一步,創建動態規劃的備忘錄,即狀態轉移矩陣
f = np.zeros((eggs+1, floors+1), dtype=np.int)
# 第二步,考慮邊界
# part1: 先考慮eggs邊界,eggs爲0,則爲0;eggs爲1,肯定從第0層往上依次實驗
for i in range(floors+1):
f[0][i] = 0
f[1][i] = i
# part2: 再考慮floor的邊界,floors爲0即爲0,floors爲1即爲1
for i in range(eggs+1):
f[i][0] = 0
f[i][1] = 1
# 第三步就是狀態方程了,雞蛋從第2個開始算,樓層也從第2開始算
for egg in range(2, eggs+1):
for floor in range(2, floors+1):
# 你還有egg個雞蛋,一共有floor層樓的子問題
# 定義一個變量來存儲最終結果,找到在哪裏扔能達到所扔次數最少的目標
result = 100000
# 從第drop層扔雞蛋
for drop in range(1, floor+1):
# 碎了,剩下的問題即如何在drop-1層,用egg-1個雞蛋尋找最優解
broken = f[egg-1][drop-1]
# 沒碎,在floors-drop層,用egg個雞蛋找最優解
unbroken = f[egg][floor-drop]
# 兩種情況取最大值,因爲我根本不知道雞蛋會不會碎
condition = max(broken, unbroken)+1
# 不斷和上一次結果做比較,得到最少的扔次數
result = min(condition, result)
f[egg][floor] = result
# 以上步驟在不斷的往狀態舉證填充,到這裏已經填充完畢
final_result = f[eggs][floors]
return final_result
if __name__ == '__main__':
print get_min_floor(eggs=2, floors=100) # 輸出結果14
print get_min_floor(eggs=2, floors=36) # 輸出結果8
print get_min_floor(eggs=2, floors=39) # 輸出結果9
print get_min_floor(eggs=3, floors=39) # 輸出結果6