高楼扔鸡蛋 -- 动规

887. 鸡蛋掉落


class SuperEggDrop:
    """
    887. 鸡蛋掉落
    // 定义:手握 K 个鸡蛋,面对 N 层楼,最少的扔鸡蛋次数为 dp(K, N)
    https://leetcode.cn/problems/super-egg-drop/description/
    """
    def solution(self, k: int, n: int) -> int:
        # m 最多不会超过 N 次(线性扫描)
        self.memo = [[-666 for _ in range(n + 1)] for _ in range(k + 1)]
        return self.dp(k, n)

    def dp(self, K, N):
        """
        时间复杂度:O(K*N^2)
        空间复杂度:O(K*N)
        定义:手握 K 个鸡蛋,面对 N 层楼,最少的扔鸡蛋次数为 dp(K, N)
        :param K:
        :param N:
        :return:
        """
        # base case
        if K == 1:  # 就剩一个鸡蛋,只能线性扫描
            return N
        if N == 0:
            return 0
        # 查备忘录避免冗余计算
        if self.memo[K][N] != -666:
            return self.memo[K][N]

        res = float('inf')
        # 在所有楼层进行尝试,取最少扔鸡蛋次数
        for i in range(1, N+1):
            res = min(res,
                      # 选择碎和没碎的最坏情况
                      max(self.dp(K, N-i), self.dp(K-1, i-1)) + 1)
        self.memo[K][N] = res
        return res

    def solution2(self, k: int, n: int) -> int:
        """
        二分搜索优化
        注意到dp(k, n)数组的定义,有k个鸡蛋面对n层楼最少需要扔多少次,很容易知道k固定时,这个函数
        随着n的增加一定是单调递增的;
        那么dp(K-1, i-1)和dp(K, N-i)这两个函数,固定K和N,随着i的增加(从1到N),前者随着i单调递增
        后者随着i单调递减,二者函数曲线的交点处,必是所需要求的最少次数,因此可以考虑使用「二分查找技巧」
        时间复杂度:O(K*N*logN)
        空间复杂度:O(K*N)
        :param k:
        :param n:
        :return:
        """
        self.memo = [[-666 for _ in range(n + 1)] for _ in range(k + 1)]
        return self.dp2(k, n)

    def dp2(self, K, N):
        """
        定义:手握 K 个鸡蛋,面对 N 层楼,最少的扔鸡蛋次数为 dp(K, N)
        :param K:
        :param N:
        :return:
        """
        # base case
        if K == 1:  # 就剩一个鸡蛋,只能线性扫描
            return N
        if N == 0:
            return 0
        # 查备忘录避免冗余计算
        if self.memo[K][N] != -666:
            return self.memo[K][N]

        # 用二分搜索代替线性搜索
        res = float('inf')
        lo, hi = 1, N
        while lo <= hi:
            mid = lo + (hi-lo)//2
            # 鸡蛋在第 mid 层碎了和没碎两种情况
            broken = self.dp2(K-1, mid-1)
            not_broken = self.dp2(K, N-mid)
            # res = min(max(碎,没碎) + 1)
            if broken > not_broken:
                hi = mid - 1
                res = min(res, broken + 1)
            else:
                lo = mid + 1
                res = min(res, not_broken + 1)

        self.memo[K][N] = res
        return res

    def solution3(self, K: int, n: int) -> int:
        """
        时间复杂度:O(K*N)
        空间复杂度:O(K*N)
        改变dp数组的定义
        dp[K][m] = N,也就是给你 K 个鸡蛋,测试 m 次,最坏情况下最多能测试 N 层楼。
        :param k:
        :param n:
        :return:
        """
        # m 最多不会超过 N 次(线性扫描)
        dp = [[0 for _ in range(n + 1)] for _ in range(K + 1)]
        # base case:
        # dp[0][..] = 0
        # dp[..][0] = 0
        m = 0
        # 结束条件 dp[K][m] == N
        while dp[K][m] < n:
            m += 1
            for k in range(1, K+1):
                dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1

        return m


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NLP_wendi

谢谢您的支持。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值