你将得到 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
}
复制代码
初始状态为只有一个鸡蛋时,各个楼层须要的次数。
待更新...