LeetCode887 - Super Egg Drop - Hard (Python)

这是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). 

相关推荐