扔鸡蛋问题

字节二面遇到了扔鸡蛋问题,之前有听说过这道题但是却没有正经看过,结果也是凉了.
先看简单的情况:鸡蛋掉落-两枚鸡蛋

给你 2 枚相同 的鸡蛋,和一栋从第 1 层到第 n 层共有 n 层楼的建筑。
已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都 会碎 ,从 f 楼层或比它低 的楼层落下的鸡蛋都 不会碎 。
每次操作,你可以取一枚 没有碎 的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

这里只说明一下动态规划的做法,关于数学方法有兴趣自己去看就好了.
使用动态规划:dp[i]表示在第i层需要的次数,那么对于第i层的求解则有两种可能

  • 如果dp[i]时候鸡蛋碎了 那么就需要从第一层开始一直到i-1层一个一个的去试 加上最初的那一次,一共就需要i次
  • 如果dp[i]时候鸡蛋没碎,那么可以看做是对于剩下的n-i层使用2枚鸡蛋求解问题,变成了一个子问题.此时的次数为1+dp[n-i]

因此对于每次层数i,都求解出此前楼层[1,i]中这两种可能的最大值,然后取最小,即是当前第i层最坏情况的最好值.

public static int twoEggDrop(int n) {
        if (n <= 2) return n;
        int[] dp = new int[n + 1];

        for (int i = 1; i <= n; i++) {
            int minMax = Integer.MAX_VALUE;
            for (int j = 1; j <= i; j++) {
                minMax = Math.min(minMax, Math.max(dp[i - j] + 1, j));
            }
            dp[i] = minMax;
        }
        return dp[n];
    }

再看进阶版的问题:鸡蛋掉落

给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。
已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

进阶版的问题是k个鸡蛋,n层楼梯的问题.如果单纯的使用前面的动态规划那么会超时,所以采用二分的方式.
这里的解题思路来自力扣的,官方已经说的很清楚了:

我们可以考虑使用动态规划来做这道题,状态可以表示成(k,n),其中k为鸡蛋数,n为楼层数。当我们从第x楼扔鸡蛋的时候:

  • 如果鸡蛋不碎,那么状态变成(k,n−x),即我们鸡蛋的数目不变,但答案只可能在上方的 n−x 层楼了。也就是说,我们把原问题缩小成了一个规模为(k,n−x) 的子问题;
  • 如果鸡蛋碎了,那么状态变成(k−1,x−1),即我们少了一个鸡蛋,但我们知道答案只可能在第x楼下方的x−1 层楼中了。也就是说,我们把原问题缩小成了一个规模为 (k-1, x-1)的子问题。

这样一来,我们定义dp(k,n) 为在状态(k,n) 下最少需要的步数。根据以上分析我们可以列出状态转移方程:
>![](https://img-blog.csdnimg.cn/75233e71527e430a871feb73f6a55f19.png)
对于dp(k,n) 而言,我们像上面分析的那样,枚举第一个鸡蛋扔在的楼层数 x。由于我们并不知道真正的 f 值,因此我们必须保证 鸡蛋碎了之后接下来需要的步数 和 鸡蛋没碎之后接下来需要的步数 二者的 最大值 最小,这样就保证了在 最坏情况下(也就是无论 f 的值如何) dp(k,n) 的值最小。
dp(k,n) 是一个关于n 的单调递增函数,也就是说在鸡蛋数 k 固定的情况下,楼层数 n 越多,需要的步数一定不会变少。在上述的状态转移方程中,第一项T1(x)=dp(k−1,x−1) 是一个随 x 的增加而单调递增的函数,第二项T2(x)=dp(k,n−x) 是一个随着x 的增加而单调递减的函数。
当一个函数单调递增而另一个函数单调递减时,我们如何找到一个位置使得它们的最大值最小呢?
在这里插入图片描述如上图所示,如果这两个函数都是连续函数,那么我们只需要找出这两个函数的交点,在交点处就能保证这两个函数的最大值最小。但在本题中T1(x) 和T2(x) 都是离散函数,也就是说,x 的值只能取1,2,3 等等。在这种情况下,我们需要找到最大的满足T1(x)<T2(x)的x0,以及最小的满足T1(x)≥T2(x) 的x1,对应到上图中,就是离这两个函数(想象中的)交点左右两侧最近的整数。
我们只需要比较在x0和x1处两个函数的最大值,取一个最小的作为x 即可。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/super-egg-drop/solution/ji-dan-diao-luo-by-leetcode-solution-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

static Map<Integer, Integer> map = new HashMap<>();

    public static int superEggDrop(int k, int n) {
        if (n == 0) return 0;
        if (k == 1) return n;
        if (map.containsKey(100 * n + k)) {
            return map.get(100 * n + k);
        }

        int ans = 0;
        int left = 1, right = n;
        while (left + 1 < right) {
            int mid = (right + left) / 2;
            int t1 = superEggDrop(k - 1, mid - 1);//对应鸡蛋碎了
            int t2 = superEggDrop(k, n - mid);//对应鸡蛋没碎
            //二分法
            if (t1 < t2) left = mid;
            else if (t1 > t2) right = mid;
            else left = right = mid;
        }

        ans = 1 + Math.min(Math.max(superEggDrop(k - 1, left - 1), superEggDrop(k, n - left)),
                Math.max(superEggDrop(k - 1, right - 1), superEggDrop(k, n - right)));
        map.put(100 * n + k, ans);
        return map.get(100 * n + k);
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值