【leetcode】鸡蛋掉落问题

在leetcode刷动态规划问题过程中,鸡蛋掉落问题是比较经典的,特别是笔试面试喜欢出的问题。腾讯,Vivo等大厂都出现过,在这里通过自己学习,以及借鉴大佬的思路,对这道题进行整理。

其它算法问题刷题总结可以参考:基础算法分类总结(持续更新中)。

文章目录

      • 一、鸡蛋掉落问题解析
        • 1. 题目描述
        • 2. 解决方法
          • 2.1 动态规划——01背包——二维数组
          • 2.2 动态规划——01背包——一维数组
          • 2.3 动态规划——二维数组
          • 2.4 动态规划——一维数组

一、鸡蛋掉落问题解析

1. 题目描述

leetcode题目链接:887. 鸡蛋掉落
【leetcode】鸡蛋掉落问题_第1张图片

2. 解决方法

遇到这个问题时,很多同学就觉得使用二分查找,不断缩减查找的区间。但直接使用二分查找去计算层数时,鸡蛋是不够的。题目中描述说,每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。可以分析发现:

一颗蛋在任一楼层扔下,分两种情况:

  1. 蛋碎了。也就是说需要向下尝试楼层。剩余蛋个数为K-1个,剩余可尝试次数为f-1次
  2. 蛋没碎。也就是说可以向上继续尝试楼层。剩下的蛋个数为K个,剩余可尝试次数为f-1次

因此可以使用动态规划来解决这道问题。

2.1 动态规划——01背包——二维数组

鸡蛋掉落的问题可以理解为动态规划中典型的01背包问题。背包:鸡蛋数(k个); 物品:操作数(n个);价值:确定楼层。01背包问题可以参考:动态规划之背包问题——01背包。

有人问了,为什么不是鸡蛋作为物品,操作数作为背包?背包问题往往物品与价值有正相关关系。鸡蛋有k个,但实际不一定全都用上,限制一定的最小操作数,鸡蛋增加,确定楼层(价值)不一定增加。而取一定的鸡蛋,操作数每增加1,确定楼层(价值)就会一定增加。

因此可以定义:dp[i][count]代表k个鸡蛋扔count层最多能测试层数

状态转移方程:当前价值 = 鸡蛋碎了的价值 + 鸡蛋没碎的价值 + 确定当前层的价值1dp[i][count] = dp[i-1][count-1]+dp[i][count-1]+1;

这样二维数组的01背包解法的如下(二维01背包的遍历顺序都可以):

class Solution {
    public int superEggDrop(int k, int n) {
        // 动态规划01背包,dp[i][count]代表k个鸡蛋扔count层最多能测试层数
        // 当前价值 = 鸡蛋碎了的价值 + 鸡蛋没碎的价值 + 确定当前层的价值1
        // dp[i][count] = dp[i-1][count-1]+dp[i][count-1]+1;
        // 背包:鸡蛋数(k个); 物品:操作数(n个);价值:确定楼层
        if (n == 1) return 1;
        int[][] dp = new int[k + 1][n + 1];
        int count = 0;
        for (; dp[k][count] < n;) { // 遍历物品,这里直到价值为n就结束
            count++;
            for (int i = k; i >= 1; i--) {
                dp[i][count] = dp[i-1][count-1] + dp[i][count-1] + 1;
            }
        }
        return count;
    }
}

对应的就是01背包动态规划的一维解法。

2.2 动态规划——01背包——一维数组

因为状态 i 只与状态 i-1 有关,int[] dp = new int[k + 1]; 一维数组的遍历顺序是先遍历物品,再遍历背包,且逆序。

class Solution {
    public int superEggDrop(int k, int n) {
        if (n == 1) return 1;
        // 一维数组
        int[] dp = new int[k + 1];
        int count = 0;
        for (; dp[k] < n;) { // 遍历物品,这里直到价值为n就结束
            count++;
            for (int i = k; i >= 1; i--) {
                dp[i] = dp[i-1] + dp[i] + 1;
            }
        }
        return count;
    }
}

这里可以对遍历做一下处理:

while (dp[k] < n) {
    count++;
    for (int i = k; i >= 1; i--) {
        dp[i] += dp[i-1] + 1;
    }
}
2.3 动态规划——二维数组

如果01背包不好理解,可以换一种理解方法,但整体思想是一致的。

状态定义dp[i][j] 表示在i步用j个鸡蛋可以测出最大层数

状态转移

  • 鸡蛋没碎,数量不变,用了一步,dp[i - 1][j](上层);
  • 鸡蛋碎了,数量减一,用了一步,dp[i - 1][j - 1](下层);
  • 1(当前层);

则最大层数 = 上层 + 下层 + 当前层:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] + 1;

class Solution {
    public int superEggDrop(int k, int n) {
        // dp[i][j] 表示在i步用j个鸡蛋可以测出最大层数
        if (n == 1) return 1;
        int[][] dp = new int[n + 1][k + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                // 鸡蛋没碎,数量不变,用了一步,dp[i - 1][j](上层)
                // 鸡蛋碎了,数量减一,用了一步,dp[i - 1][j - 1](下层)
                // 1(当前层)
                // 最大层数 = 上层 + 下层 + 当前层
                dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] + 1;
                if (dp[i][j] >= n) return i;
            }
        }
        return n;
    }
}
2.4 动态规划——一维数组

同样的因为本次状态至于鸡蛋有关,可以优化二维数组为一维数组。

class Solution {
    public int superEggDrop(int k, int n) {
        // dp[j] 表示用j个鸡蛋可以测出最大层数
        if (n == 1) return 1;
        // 一维数组
        int[] dp = new int[k + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = k; j > 0; j --) {  // 转一维需要逆序
                // 鸡蛋没碎,数量不变,用了一步,dp[j](上层)
                // 鸡蛋碎了,数量减一,用了一步,dp[j - 1](下层)
                // 1(当前层)
                // 最大层数 = 上层 + 下层 + 当前层
                dp[j] += dp[j - 1] + 1;
                if (dp[j] >= n) return i;
            }
        }
        return n;
    }
}

总结:

初看到这道题很容易被二分查找缩小区间给误导,这道题用动态规划解决需要自己分析其中的状态转换的过程,列出状态转移方程。

参考:

记录Leetcode 鸡蛋掉落 的思路

鹰蛋问题(leetcode 887 鸡蛋掉落)

官方题解:鸡蛋掉落

你可能感兴趣的:(算法分析,动态规划,算法,leetcode,鸡蛋掉落)