这是labuladong的文章总结。
这是一题经典的DP问题,DP的框架为首先思考这个问题有什么状态,有哪些选择,最后根据情况穷举所有可行解。
这题的状态即为当前拥有的鸡蛋数k以及当前所在的楼层数n。选择是我们选择去哪一层楼扔鸡蛋。由状态和选择我们可以得到相对应的转移方程(因为有两个状态,鸡蛋数以及楼层数,所以我们可以考虑用二维的数组):
dp[k][i] = min(dp[k][i], max(dp[k-1][i-1], dp[k][n-i]) 在i层楼扔鸡蛋,鸡蛋可能碎也可能不碎(状态转移方程从这里产生!!!以后遇到对每一步都有两个状态的题,可以多思考,看是否是DP)。如果鸡蛋碎了,那么我们可以用的鸡蛋数就要-1, 因为我们此时只有k-1个鸡蛋了。楼层变为了1....i- 1.(dp[k-1][i- ]) 如果鸡蛋没碎,那么当前我们有的鸡蛋还是k个,但是楼层变成了i+1...n(dp[k][n-i])。 所以dp[k][i] 等于这两种情况的最大值+1.(记得要+1, 因为我们需要执行在i层k个鸡蛋扔鸡蛋的动作),把这两个状态的最大值和dp[k][i] 进行对比。
时间复杂度:动态规划算法的时间复杂度是子问题个数 * 函数本身的复杂度。子问题个数是K*N, 因为是两种状态的组合。函数本身的复杂度,是一个for遍历,O(N)。所以是O(K*N^2)
空间复杂度:子问题的个数 O(KN)
但是这种最直接的解法会超时。
class Solution: def superEggDrop(self, K: int, N: int) -> int: self.memo = {} return self.dp(K, N) def dp(self, K, N): if (K, N) in self.memo: return self.memo[(K, N)] if K == 1: return N if N == 0: return 0 res = float(‘inf‘) for i in range(1, N+1): res = min(res, max(self.dp(K-1, i-1), self.dp(K, N-i)) + 1) self.memo[(K, N)] = res return res
优化版本。
在上述方法的基础上,我们可以观察到dp(k-1, i-1) dp(k, n-i) 这两个函数在k和n确定的情况下是关于i的单调函数,其中前者是单调增,后者是单调减函数。那么当这两个函数相交的时候,即为
min(max(self.dp(k-1, i-1), self.dp(k, n-i))。
class Solution: def superEggDrop(self, K: int, N: int) -> int: self.memo = {} return self.dp(K, N) def dp(self, K, N): if (K, N) in self.memo: return self.memo[(K, N)] if K == 1: return N if N == 0: return 0 res = float(‘inf‘) l = 1 r = N while l <= r: mid = l + (r-l) // 2 broke = self.dp(K-1, mid-1) not_broken = self.dp(K, N-mid) if broke > not_broken: r = mid -1 res = min(res, broke+1) # 注意是取max(broke, not_broken) else: l = mid + 1 res = min(res, not_broken+1) self.memo[(K, N)] = res return res
时间复杂度:子问题的个数 * 函数本身的复杂度。子问题的个数是k * n, 函数本身的复杂度是二分法O(logn). O(KN*Logn).
空间复杂度:子问题的个数 O(KN).
原文地址:https://www.cnblogs.com/sky37/p/12247772.html