887. 鸡蛋掉落
class SuperEggDrop:
"""
887. 鸡蛋掉落
// 定义:手握 K 个鸡蛋,面对 N 层楼,最少的扔鸡蛋次数为 dp(K, N)
https://leetcode.cn/problems/super-egg-drop/description/
"""
def solution(self, k: int, n: int) -> int:
self.memo = [[-666 for _ in range(n + 1)] for _ in range(k + 1)]
return self.dp(k, n)
def dp(self, K, N):
"""
时间复杂度:O(K*N^2)
空间复杂度:O(K*N)
定义:手握 K 个鸡蛋,面对 N 层楼,最少的扔鸡蛋次数为 dp(K, N)
:param K:
:param N:
:return:
"""
if K == 1:
return N
if N == 0:
return 0
if self.memo[K][N] != -666:
return self.memo[K][N]
res = float('inf')
for i in range(1, N+1):
res = min(res,
max(self.dp(K, N-i), self.dp(K-1, i-1)) + 1)
self.memo[K][N] = res
return res
def solution2(self, k: int, n: int) -> int:
"""
二分搜索优化
注意到dp(k, n)数组的定义,有k个鸡蛋面对n层楼最少需要扔多少次,很容易知道k固定时,这个函数
随着n的增加一定是单调递增的;
那么dp(K-1, i-1)和dp(K, N-i)这两个函数,固定K和N,随着i的增加(从1到N),前者随着i单调递增
后者随着i单调递减,二者函数曲线的交点处,必是所需要求的最少次数,因此可以考虑使用「二分查找技巧」
时间复杂度:O(K*N*logN)
空间复杂度:O(K*N)
:param k:
:param n:
:return:
"""
self.memo = [[-666 for _ in range(n + 1)] for _ in range(k + 1)]
return self.dp2(k, n)
def dp2(self, K, N):
"""
定义:手握 K 个鸡蛋,面对 N 层楼,最少的扔鸡蛋次数为 dp(K, N)
:param K:
:param N:
:return:
"""
if K == 1:
return N
if N == 0:
return 0
if self.memo[K][N] != -666:
return self.memo[K][N]
res = float('inf')
lo, hi = 1, N
while lo <= hi:
mid = lo + (hi-lo)//2
broken = self.dp2(K-1, mid-1)
not_broken = self.dp2(K, N-mid)
if broken > not_broken:
hi = mid - 1
res = min(res, broken + 1)
else:
lo = mid + 1
res = min(res, not_broken + 1)
self.memo[K][N] = res
return res
def solution3(self, K: int, n: int) -> int:
"""
时间复杂度:O(K*N)
空间复杂度:O(K*N)
改变dp数组的定义
dp[K][m] = N,也就是给你 K 个鸡蛋,测试 m 次,最坏情况下最多能测试 N 层楼。
:param k:
:param n:
:return:
"""
dp = [[0 for _ in range(n + 1)] for _ in range(K + 1)]
m = 0
while dp[K][m] < n:
m += 1
for k in range(1, K+1):
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1
return m