你將得到 K 個雞蛋,並能夠使用一棟從 1 到 N 共有 N 層樓的建築。git
每一個蛋的功能都是同樣的,若是一個蛋碎了,你就不能再把它掉下去。github
你知道存在樓層 F ,知足 0 <= F <= N 任何從高於 F 的樓層落下的雞蛋都會碎,從 F 樓層或比它低的樓層落下的雞蛋都不會破。算法
每次移動,你能夠取一個雞蛋(若是你有完整的雞蛋)並把它從任一樓層 X 扔下(知足 1 <= X <= N)。swift
你的目標是確切地知道 F 的值是多少。數組
不管 F 的初始值如何,你肯定 F 的值的最小移動次數是多少?ide
提示:優化
1 <= K <= 100ui
1 <= N <= 10000spa
leetcode連接code
若是隻有一個蛋, F的值是多少?
假設樓有N層高,只能從1層挨個丟到N層,直到雞蛋破裂爲止。
有朋友會問若是第一層雞蛋就壞了,那麼是否最小移動次數爲1便可?
請注意有個關鍵信息 不管 F 的初始值如何
, 因此最小值確認次數只能爲N。
無數個蛋,就能夠用最快的二分方法。
與下面這題的思路一致:
次數爲O(logN),向上取整。
這題在leetcode上看到以前,第一次接觸到的是兩個蛋的版本。
假設如今有100層樓,2個雞蛋,怎麼丟是最好的?
這題最關鍵是要考慮最差的狀況。
假設F爲49,使用二分法,50層蛋碎了,那麼須要從1-49層挨個丟一次,結果爲1 + 49 = 50次。
假設F爲74,四等份方式,25, 50,75各丟一次而後從51-74,24 + 3 = 27次。
也有說直接對層數開方,那麼最大就是9 + 9 了。(89層)
這裏的次數實際上是蛋碎次數的最大值(如分紅10分,則最多第9次碎),加上單個分段的最大值(0-10爲一段,則最多從1-9,9次)的和。
因此爲了儘量平均大小,成立一個等式 1+2+3...+X >= N。 X爲知足等式的最小值。
從第N層開始丟,若沒碎則從N + (N - 1)層開始丟,以此類推。
若第一次壞了,總次數爲1 + (N - 1),若第二次壞了,則爲 2 + (N - 2),次數保證爲N。
那麼若是N爲100,能夠求得X的值爲14。
原文檔代碼中有一點小問題,下面是已經修正的代碼:
public func drop(numberOfEggs: Int, numberOfFloors: Int) -> Int {
guard numberOfEggs != 0 && numberOfFloors != 0 else { return 0 }
guard numberOfEggs != 1 && numberOfFloors != 1 else { return 1 }
var eggFloor: [[Int]] = .init(repeating: .init(repeating: 1, count: numberOfFloors + 1), count: numberOfEggs + 1)
var attempts = 0
for floorNumber in stride(from: 0, through: numberOfFloors, by: 1) {
eggFloor[1][floorNumber] = floorNumber
}
for eggNumber in stride(from: 2, through: numberOfEggs, by: 1) {
for floorNumber in stride(from: 2, through: numberOfFloors, by: 1) {
eggFloor[eggNumber][floorNumber] = Int.max
for visitingFloor in stride(from: 1, through: floorNumber, by: 1) {
attempts = 1 + max(eggFloor[eggNumber - 1][visitingFloor - 1], eggFloor[eggNumber][floorNumber - visitingFloor])
if attempts < eggFloor[eggNumber][floorNumber] {
eggFloor[eggNumber][floorNumber] = attempts
}
}
}
}
return eggFloor[numberOfEggs][numberOfFloors]
}
複製代碼
該思路是經過狀態轉移的方式求得。
主要分兩步:
var eggFloor: [[Int]] = .init(repeating: .init(repeating: 1, count: numberOfFloors + 1), count: numberOfEggs + 1)
var attempts = 0
for floorNumber in stride(from: 0, through: numberOfFloors, by: 1) {
eggFloor[1][floorNumber] = floorNumber
}
複製代碼
初始化一個二維數組,經過eggFloor[eggNumber][floorNumber]的方式存儲或者獲取當前的結果。
初始化一個蛋的存儲內容。
attempts = 1 + max(eggFloor[eggNumber - 1][visitingFloor - 1], eggFloor[eggNumber][floorNumber - visitingFloor])
if attempts < eggFloor[eggNumber][floorNumber] {
eggFloor[eggNumber][floorNumber] = attempts
}
複製代碼
若是丟第N層時,蛋碎了,那麼次數爲 (當前蛋的數量-1)在 (N - 1)層中的移動次數 + 1。
若是蛋沒有碎,次數爲(當前蛋的數量)在 (總層數 - N)層中的移動次數 + 1。
思路比較簡單清晰,但有一個時間複雜度的問題,這也是爲何稱爲暴力DP的緣由,在3個for循環套用下來只有,會發現時間複雜度可以達到kN²,也就是蛋數量 * 樓層數量 * 樓層數量。
有想法的朋友能夠嘗試用DP的思路作一次。
斐波那契數列若是用DP的思路能夠分爲兩種。
一種是從前到後推:
f(3) = f(2) + f(1)
f(4) = f(3) + f(2)
......
f(10) = f(9) + f(8)
一種是從後往前:
f(10) = f(9) + f(8)
f(10) = (f(8) + f(7)) + (f(7) + f(6))
......
其中第一種時間複雜度爲N, 第二種爲2的N次方。
func superEggDrop(_ K: Int, _ N: Int) -> Int {
var dp = [Int](repeating: 0, count: N + 1)
for floorNum in stride(from: 0, through: N, by: 1) {
dp[floorNum] = floorNum
}
for _ in stride(from: 2, through: K, by: 1) {
var dp2 = [Int](repeating: 0, count: N + 1)
var x = 1
for n in stride(from: 1, through: N, by: 1) {
while x < n && max(dp[x - 1], dp2[n - x]) > max(dp[x], dp2[n - x - 1]) {
x += 1
}
dp2[n] = 1 + max(dp[x - 1], dp2[n - x])
}
dp = dp2
}
return dp[N]
}
複製代碼
以前的dp初始值爲[eggNumber][floorNumber]的二維數組方式存儲。
如今只根據[floorNumber]存儲。
var dp = [Int](repeating: 0, count: N + 1)
for floorNum in stride(from: 0, through: N, by: 1) {
dp[floorNum] = floorNum
}
複製代碼
初始狀態爲只有一個雞蛋時,各個樓層須要的次數。
待更新...