DP、二分-LeetCode887. 鸡蛋掉落(Python)

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 单增的,如果我们固定 KN把这两个函数看做关于 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://leetcode-cn.com/problems/super-egg-drop/solution/ji-ben-dong-tai-gui-hua-jie-fa-by-labuladong/

https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/gao-lou-reng-ji-dan-jin-jie

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值