1、题目描述
鸡蛋没摔坏可以继续使用!相似笔试题:黑客入侵点检测
2、代码详解
法三:复杂度 O(KN),重新定义状态转移(推荐)
class Solution(object):
def superEggDrop(self, K, N):
# 原问题:给你 K 鸡蛋,N 层楼,让你求最坏情况下最少的测试次数 m
# 法三:重新定义状态
'''
给你 K 个鸡蛋,测试 m 次,最坏情况下最多能测试 N 层楼
dp[k][m] = n
当前有 k 个鸡蛋,可以尝试扔 m 次鸡蛋,这个状态下,最坏情况下最多能确切测试一栋 n 层的楼
'''
# while 循环结束的条件是 dp[K][m] == N
# m 最多不会超过 N 次(线性扫描)。m 为什么要减一而不是加一?定义这个 m 是一个允许的次数上界,而不是扔了几次
dp = [[0] * (N+1) for _ in range(K+1)] # [K + 1]*[N + 1]
m = 0
while dp[K][m] < N:
m += 1
for k in range(1, K+1): # 1~K
# 无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上
# 无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)
# dp[k][m - 1] 就是楼上的楼层数,因为鸡蛋个数 k 不变,也就是鸡蛋没碎,扔鸡蛋次数 m 减一
# dp[k - 1][m - 1] 就是楼下的楼层数,因为鸡蛋个数 k 减一,也就是鸡蛋碎了,同时扔鸡蛋次数 m 减一
dp[k][m] = dp[k][m-1] + dp[k-1][m-1] + 1
return m
法一:穷举所有可能,O(K*N*N),超时!
class Solution(object):
def superEggDrop(self, K, N):
memo = dict() # key为(K,N),value为尝试次数
def dp(K, N): # K个鸡蛋、N层楼
if K == 1: # 当鸡蛋数 K 为 1 时,显然只能线性扫描所有楼层
return N
if N == 0: # 当楼层数 N 等于 0 时,显然不需要扔鸡蛋
return 0
if (K, N) in memo: # memo避免重复计算
return memo[(K, N)]
# 法一: 穷举所有可能,时间复杂度是 O(K*N*N)
res = float('INF')
for i in range(1, N+1):
res = min(res,
max(
dp(K - 1, i - 1), # 碎
dp(K, N - i) # 没碎
) + 1 # 在i楼扔了一次
)
memo[(K, N)] = res
return res
return dp(K, N)
法二:优化法一,用二分搜索代替线性搜索,时间复杂度降为 O(K*N*logN)
首先我们根据 dp(K, N)
数组的定义(有 K
个鸡蛋面对 N
层楼,最少需要扔几次),很容易知道 K
固定时,这个函数随着 N
的增加一定是单调递增的,无论你策略多聪明,楼层增加测试次数一定要增加。
那么注意 dp(K - 1, i - 1)
和 dp(K, N - i)
这两个函数,其中 i
是从 1 到 N
单增的,如果我们固定 K
和 N
,把这两个函数看做关于 i
的函数,前者随着 i
的增加应该也是单调递增的,而后者随着 i
的增加应该是单调递减的。
这时候求二者的较大值,再求这些最大值之中的最小值,其实就是求这两条直线交点,也就是红色折线的最低点嘛。
class Solution(object):
def superEggDrop(self, K, N):
memo = dict() # key为(K,N),value为尝试次数
def dp(K, N): # K个鸡蛋、N层楼
if K == 1: # 当鸡蛋数 K 为 1 时,显然只能线性扫描所有楼层
return N
if N == 0: # 当楼层数 N 等于 0 时,显然不需要扔鸡蛋
return 0
if (K, N) in memo: # memo避免重复计算
return memo[(K, N)]
# 法一: 穷举所有可能,时间复杂度是 O(K*N*N)
# res = float('INF')
# for i in range(1, N+1):
# res = min(res,
# max(
# dp(K - 1, i - 1), # 碎
# dp(K, N - i) # 没碎
# ) + 1 # 在i楼扔了一次
# )
# 法二优化:用二分搜索代替线性搜索,时间复杂度降为 O(K*N*logN)
res = float('INF')
low, high = 1, N
while low <= high:
mid = (low + high) // 2
broken = dp(K - 1, mid - 1) # 碎
not_broken = dp(K, N - mid) # 没碎
# res = min(max(碎,没碎) + 1)
if broken > not_broken:
high = mid - 1
res = min(res, broken + 1)
else:
low = mid + 1
res = min(res, not_broken + 1)
memo[(K, N)] = res
return res
return dp(K, N)
推荐题解 labuladong
https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/gao-lou-reng-ji-dan-jin-jie