887. 鸡蛋掉落
https://leetcode-cn.com/problems/super-egg-drop/
解法一:dp
我们在第i层楼扔了鸡蛋之后,可能出现两种情况:鸡蛋碎了,鸡蛋没碎。注意,这时候状态转移就来了: 如果鸡蛋碎了,那么鸡蛋的个数K应该减一,搜索的楼层区间应该从[1..N]变为[1..i-1]共i-1层楼; 如果鸡蛋没碎,那么鸡蛋的个数K不变,搜索的楼层区间应该从 [1..N]变为[i+1..N]共N-i层楼。
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函数改进
上面代码对用例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比较
重写状态转移
之前定义的dp数组含义:
用 dp 数组表示的话也是一样的:
即:确定当前的鸡蛋个数和楼层数,就知道最小扔鸡蛋次数
现将dp定义修改为:确定当前鸡蛋个数和最多允许的扔鸡蛋次数,求通过这两个条件确定的最高楼层数
比如说 dp[1][7] = 7 表示: 现在有 1 个鸡蛋,允许你扔 7 次; 这个状态下最多给你 7 层楼, 使得你可以确定楼层 F 使得鸡蛋恰好摔不碎 (一层一层线性探查)
最终要求的其实是扔鸡蛋次数m,但是这时候m在状态之中而不是dp数组的结果。因此题目就是给K鸡蛋,N层楼,让你求最坏情况下最少的测试次数m,while循环结束的条件是dp[K][m] == N
原始解法还得线性或者二分扫描所有楼层,要求最大值、最小值。但是现在这种dp定义根本不需要这些了,基于下面两个事实:
1、无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上。
2、无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)。
得状态转移方程:
由方程可知dp[k][m]依赖左边和左上的元素,因此遍历应以列为优先顺序 最后求的是使dp[K][m]>=N的最小m,因此循环退出时,要考虑dp[K][m]与N的关系
最后更新于