每日一题:鸡蛋掉落

58 篇文章 1 订阅
44 篇文章 0 订阅

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。
每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。
你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。
每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。
你的目标是确切地知道 F 的值是多少。
无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

这题是在太抽象了,直接看题解吧:鸡蛋掉落题解

方法一:动态规划+二分搜索

使用动态规划解决这道题,状态可以表示为(K,N),其中K为鸡蛋数,N为楼层数,(K,N)表示为确定F所需的最小操作数。当我们从第X楼扔鸡蛋的时候:

  1. 如果鸡蛋不碎,那么状态变为(K,N - X),即我们鸡蛋的数目不变,但答案只可能在上方的N-X层楼了。也就是说,我们把原问题缩小成了一个规模为(K,N - X)的子问题;
  2. 如果鸡蛋碎了,那么状态变成(K - 1,X - 1),少了一个鸡蛋,但答案只可能在第X楼下方的X - 1层楼中了。所以原问题被缩小为了一个规模为(K - 1,X - 1)的子问题。

所以状态转移方程为: d p ( K , N ) = 1 + min ⁡ 1 < = X < = N ( m a x ( d p ( K − 1 , X − 1 ) , d p ( K , N − X ) ) ) dp(K, N) = 1 + \min_{1<=X<=N}(max(dp(K - 1, X - 1), dp(K, N - X))) dp(K,N)=1+1<=X<=Nmin(max(dp(K1,X1),dp(K,NX)))
对于dp(K,N)而言,我们像上面分析的那样,枚举第一个鸡蛋仍在的楼层数X。由于我们并不知道真正的F值,因此我们必须保证鸡蛋碎了之后接下来需要的步数鸡蛋没碎之后接下来需要的步数二者的最大值最小,这样就保证了在最坏情况下(无论F的值如何) dp(K,N)的值最小。

如果我们直接暴力转移求解每个状态的dp值,时间复杂度是为O(KN^2),即一共有O(KN)
个状态,对于每个状态枚举扔鸡蛋的楼层X,需要O(N)的时间。需要优化枚举的时间复杂度。

dp(K,N)是一个关于N的单调递增函数,也就是说在鸡蛋数K固定的情况下,楼层数越多,需要的步数一定不会变少。因此,在上述的状态转移方程中,第一项dp(K - 1, X - 1)是一个随X的增加而单调递增的函数,第二项dp(K, N - X)是一个随着X的增加而单调递减的函数。

两个函数一个递增一个递减,可以通过二分查找找到交点,将枚举时间优化为O(logN)。因为是离散的,所以要比较交点两侧的点那个大那个小。

class Solution {
    unordered_map<int, int> memo;
    int dp (int K, int N) {
        if (memo.find(N * 100 + K) == memo.end()) {
            // dp表中不存在
            int ans;
            if (N == 0) ans = 0;
            else if (K == 1) ans = N;
            else {
                int lo = 1, hi = N; // 开始二分查找
                while (lo + 1 < hi) { // 找到lo和hi相邻的lo
                    int x = (lo + hi) / 2;
                    int t1 = dp(K - 1, x - 1);
                    int t2 = dp(K, N - x);

                    if (t1 < t2) lo = x;
                    else if (t1 > t2) hi = x;
                    else lo = hi = x;
                }
                ans = 1 + min(max(dp(K - 1, lo - 1), dp(K, N - lo)), max(dp(K - 1, hi - 1), dp(K, N - hi)));
            }
            memo[N*100 + K] = ans;
        }
        return memo[N*100 + K];
    }

public:
    int superEggDrop(int K, int N) {
        return dp(K, N);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值