887. 鸡蛋掉落
方法1:动态规划
最重要的是变换思想,从知道鸡蛋数K,知道层数N,求最少次数M;转化为知道鸡蛋数K,假设最多只能扔M次,求最大能排除的层数。
创建一个二维列表dp[K][N]来记录最大能排除层数(M<=N,即使一层一层扔,最多也只会扔N次,保证不会溢出)。
dp[0][:]=0 : 0个鸡蛋,无论扔多少次,只能排除0层数;
dp[:][0]=0 : 无论多少鸡蛋,扔0次,只能排除0层数。
某一状态,总共有k个鸡蛋,最多扔m次,要找的那一层为第F层。因为dp记录的是最大能排除的层数,所以我们扔的这一层,是最佳的。也就是,能排除F在楼上的情况:蛋没碎,那么还有k个鸡蛋,可以扔m-1次;能排除F在楼下的情况:蛋碎了,那么还有k-1个鸡蛋,可以扔m-1次;能排除F就在当前楼。
dp[k][m] = dp[k][m-1]+dp[k-1][m-1]+1
知道了上面的转移方程,我们可以用两个循环,外循环是m一次一次增加,内循环是k一个一个增加。当某一循环中,扔K个鸡蛋可以排除超过N层了,那么说明,当前的m次就足够了。
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
dp = [[0] * (K + 1) for _ in range(N + 1)]
for m in range(1, N + 1):
for k in range(1, K + 1):
dp[k][m] = dp[k][m-1] + dp[k - 1][m - 1] + 1
if dp[k][m] >= N:
return m
方法2.递归解法
1.定义状态f(k,n):有k个鸡蛋和n层,f(k,n)代表找到临界楼层需要的最小移动次数
2.鸡蛋只有碎/不碎两种情况。
3.将鸡蛋扔在x层,若碎,则在[1...x-1]层找(共x-1层),鸡蛋数-1;
4.若不碎,则在[x+1...n]层找(共n-x层)
5.我们需要找到最坏的一种情况,因此将鸡蛋扔在x层时,找到临界楼层需要的最小移动次数为fx(k,n) = max{fx(k-1,x-1),fx(k,n-x)},x∈[1,n]
6.可知f(k,n)应该为所有情况中的最小值,即f(k,n) = min{f1(k,n),f2(k,n)...fn(k,n)}
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
def parse(k, n):
if n == 1: # 如果只有1层,不管有多少蛋只需试1次
return 1
elif n == 0:
return 0
elif k == 1: # 只有1个鸡蛋,则只能逐层试
return n
elif (k, n) in table:
return table[(k, n)]
f = float('inf') # 定义一个无限大数作为初始条件
for x in range(1, n + 1): # 将鸡蛋扔在第x层,从第1层开始
fx = 1 + max(parse(k - 1, x - 1), parse(k, n - x))
f = min(f, fx)
table[(k, n)] = f
return f
table = {} # 记忆被计算过的情况
return parse(k, n)
时间复杂度:O(kn^2)
优化:
- 观察可知fx(k-1,x-1)随着x增加单调递增,fx(k,n-x)随着x增加单调递减,因此可以用二分查找出最坏的情况
即把找出最小值的代码
f = float('inf') # 定义一个无限大数作为初始条件
for x in range(1, n + 1): # 将鸡蛋扔在第x层,从第1层开始
fx = 1 + max(parse(k - 1, x - 1), parse(k, n - x))
f = min(f, fx)
改为:
while lp <= rp:
mid = lp + (rp-lp) // 2 # 二分法优化
bcase = parse(k-1, mid-1) # 蛋碎的情况
notbcase = parse(k,n-mid) # 不碎的情况
# fx = 1 + max(bcase, notbcase)
if bcase > notbcase:
rp = mid - 1
f = min(f, bcase + 1)
else:
lp = mid + 1
f = min(f,notbcase + 1)
时间复杂度:O(knlogn)