leetcode887. 鸡蛋掉落(动态规划-java)

文章介绍了LeetCode第887题——鸡蛋掉落的问题,探讨了如何使用暴力递归结合二分查找以及动态规划来解决此问题,以找到确定鸡蛋不会破碎的最低楼层所需的最小操作次数。动态规划和二分查找的结合能有效优化算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

leetcode887. 鸡蛋掉落

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/super-egg-drop

题目描述

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

示例 1:
输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。
如果它没碎,那么肯定能得出 f = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。

示例 2:
输入:k = 2, n = 6
输出:3

示例 3:
输入:k = 3, n = 14
输出:4

提示:
1 <= k <= 100
1 <= n <= 10000

暴力递归 + 二分查找

这道题要用动态规划解,但题目本身很难直接推出动态规划的状态转移方程。
所以先用暴力递归尝试写出暴力版本。不过用缓存加二分查找优化后,效率还是可以的,

递归时,首先要考虑的就是递归过程中的变量,这题很容易发现变化的是鸡蛋的个数和楼层,选中一个楼层扔鸡蛋,没破的话呢就去上面楼层继续测试,(鸡蛋个数K 不变,楼层变成n - i):
process(n - i,k);
鸡蛋破了的话,要去下面楼层继续测试:鸡蛋个数减一,楼层减一
process(i - 1,k - 1).
用一个图演示,就容易看懂了:
在这里插入图片描述
我们先取每个楼层的最差情况,然后选取中所有楼层最差情况下的最好情况,就是需要最少的次数了。

然后再看下为什么可以进行二分查找,首先找到,要进行二分查找,必须要满足具有单调性,首先想这样一个问题,如果鸡蛋个数固定时,楼层越多,需要测试的次数就越多,因此这个递归具有单调性,
process(n - i,k); i 越大,值会越小,
process(i - 1,k - 1). i 越大值也会越大,
如图演示:
在这里插入图片描述
其实就是求交点。

代码演示

   public int superEggDrop(int k, int n) {
          int[][]dp = new int[n + 1][k + 1];
          return process(n,k,dp);
      
    }
   
	/**
	* 递归加 二分优化
	*/
     public static int process(int n,int k,int[][]dp){
        if(n == 0){
            return 0;
        }
        if(k == 1){
            return n;
        }
        //缓存
        if(dp[n][k] != 0){
            return dp[n][k];
        }
        int res = Integer.MAX_VALUE;
        int l = 1;
        int r = n;
        while(l <= r){
            int mid = (l + r) / 2;
            //没碎
            int p1 = process(n - mid,k,dp);
            //碎了
            int p2 = process(mid - 1,k - 1,dp);
            if(p2 > p1){
               r = mid - 1;
               res = Math.min(res,p2 + 1 ) ;
            }else{
                l = mid + 1;
                res = Math.min(res,p1 + 1);
            }
        }
        dp[n][k] = res;
        return res;
    }

动态规划

动态规划时,我们换一个思路去做。
就是我们知道鸡蛋个数和面对的楼层时,就知道最小的扔鸡蛋次数,我们最终要的答案就是dp(k,n).
现在我们修改下dp的含义,确定鸡蛋个数和最多允许的扔鸡蛋次数,就能确定楼的最高层数,具体就是:
dp[k][m] = n;
当前有k个鸡蛋,最多可以尝试扔m次。
比如dp[1][7] = 7
表示一个鸡蛋,最多扔七次,这个状态下楼层最高七层,
这样我们可以得出另外一个状态转移方程:
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
如图演示:
在这里插入图片描述
dp[k][m - 1]就是上面的楼层数,因为鸡蛋没碎,才可以上去,鸡蛋个数不变,扔鸡蛋次数减一。
dp[k - 1][m - 1],就是楼下的层数,鸡蛋碎了去楼下,k -1,扔鸡蛋的次数 m - 1.
最后加1 ,加上本身楼层。
根据这个状态转移方程就可以写代码了。

代码演示

/**
* 动态规划
*/
  public int superEggDrop7(int k, int n) {
        int[][]dp = new int[k + 1][n + 1];
        int m = 0;
        //楼层 == n 时,退出,
        while(dp[k][m] < n){
            m++;
            for(int i = 1; i <= k;i++){
                dp[i][m] = dp[i][m - 1] + dp[i - 1][m - 1] + 1;
            }
        }
        //m 扔的次数。
        return m;
    }

动态规划专题

leetcode1884. 鸡蛋掉落-两枚鸡蛋

leetcode72. 编辑距离

leetcode1049. 最后一块石头的重量 II

leetcode312. 戳气球

leetcode63. 不同路径 II

leetcode62. 不同路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值