class Solution:
def superEggDrop(self, K: int, N: int):
memo = dict()
#定义状态转移函数
def dp(K, N) -> int:
# base case
if K == 1: return N
if N == 0: return 0
# 避免重复计算
if (K, N) in memo:
return memo[(K, N)]
res = float('INF')
# 穷举所有可能的选择
for i in range(1, N + 1):
res = min(res,
max(
dp(K, N - i), #没碎
dp(K - 1, i - 1) #碎
) + 1 #在第i楼扔了一次
)
# 记入备忘录
memo[(K, N)] = res
return res
return dp(K, N) #调用dp函数
改进
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
memo = {}
def dp(k, n):
if k == 1: return n
if n == 0: return 0
if (k, n) in memo:
return memo[(k, n)]
res = sys.maxsize
l = 1
r = n
#用二分优化
while l <= r:
mid = l + (r - l) // 2
broken = dp(k - 1, mid - 1) # 碎,随mid递增
not_broken = dp(k, n - mid) # 没碎,随mid递减
if broken > not_broken:
r = mid - 1
res = min(res, broken + 1) #注意加1
else:
l = mid + 1
res = min(res, not_broken + 1) #注意加1
memo[(k, n)] = res
return res
return dp(k, n)
重写状态转移
之前定义的dp数组含义:
def dp(k, n) -> int
# 当前状态为 k 个鸡蛋,面对 n 层楼
# 返回这个状态下最少的扔鸡蛋次数
用 dp 数组表示的话也是一样的:
dp[k][n] = m
# 当前状态为 k 个鸡蛋,面对 n 层楼
# 这个状态下最少的扔鸡蛋次数为 m
即:确定当前的鸡蛋个数和楼层数,就知道最小扔鸡蛋次数
现将dp定义修改为:确定当前鸡蛋个数和最多允许的扔鸡蛋次数,求通过这两个条件确定的最高楼层数
dp[k][m] = n
# 当前有 k 个鸡蛋,可以尝试扔 m 次鸡蛋
# 这个状态下,最坏情况下最多能确切测试一栋 n 层的楼
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
# m最多不会超过N次(线性扫描)
dp = [[0] * (N + 1) for _ in range(K + 1)]
m=0
# m<N防止纵坐标越界
while m < N and 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
if dp[K][m-1] < N:
return m
elif dp[K][m-1] == N:
return m-1
上面代码对用例4,5000会报超时,递归栈爆了。可以利用dp函数的单调性(见下图),将求 max(dp(K, N - i), dp(K - 1, i - 1)) 的过程优化为二分,找最低点 注意之前求的是 res = min(res, max(dp(K, N - i), dp(K - 1, i - 1)) + 1) 因此二分过程中求的dp要加一再与res比较