经典动规鸡蛋掉落问题

最近在复习动态规划,然后就想看一下自己的成果,就去力扣翻了下这道题。比较经典
贴个力扣链接:https://leetcode-cn.com/problems/super-egg-drop/
还记得刚开始看的时候,看的一知半解,现在又看这道题,来写一下自己的思路和心得
首先,读题,我个人倾向于读完题,然后去看实例加深一下理解。当只有一个鸡蛋的时候,那要试出来肯定得一层一层爬,然后最差的情况就是,爬到了顶楼鸡蛋才碎,或者没碎。所以n层楼一个鸡蛋,最少需要n次,这是毫无疑问的。
然后就是两个蛋的情况了,这个时候我有两个蛋,我可以在中间找个楼层去扔,如果运气差,碎了,那我就只有一个蛋了,唉,这是不是可以回归到一个蛋的情况,比方说我在m层扔碎了,那么我还需要m-1次,总共加起来是m次吧。然后思考一下,我有两个蛋的时候,我经历了什么,先扔碎一个蛋,把问题简化为一个蛋,巧了,这不正好可以用到动规么。dp[k][m]表示k个蛋在m层需要的次数。比方说这个例子里,我在m层扔了蛋,碎掉了,我去取dp[1][m-1] = m-1;是吧,那么dp[2][n] = dp[1][m-1]+1;
不过这个表达式还是有问题,里面还有m和n,我们需要找到mn的对应关系,不能乱扔是吧,比方说我在五楼扔蛋和九楼扔蛋,结果就不一样。
我一开始想的比较复杂,想求出这个分割点,然后每次去取,如果求出来了,运行速度估计还是很快的,但是这很不算法。我觉得写算法的人是不要参与这种复杂的脑力的,它只需要让电脑去机械的执行出答案来好了。然后发现,如果我在m层蛋没碎,那么问题是不是可以看成两个蛋,m-n层需要多少次呢。
好,现在解法很明显了,中间选个点,分情况讨论。

  • 蛋碎了,需要dp[k-1][m]次
  • 蛋没碎,需要dp[k][n-m]次
    初步想法就是,在这里遍历,便利出最短的次数,给dp[k][n]赋值。
    代码实现如下
public int superEggDrop(int k, int n) {
        if(k==1){
            return n;
        }
        int[][] dp = new int[k+1][n+1];
        for(int i=1;i<n+1;i++){
            dp[1][i] = i;
        }
        for(int i=2;i<k+1;i++){
            for(int j=1;j<n+1;j++){
                int min = dp[i-1][j];
                for(int l=2;l<j;l++){
                    min = Math.min(min, Math.max(dp[i-1][l-1]+1, dp[i][j-l]+1));
                }
                dp[i][j] = min;
            }
        }
        return dp[k][n];
    }

这里三重循环,拿去力扣跑一下,果然超时了。
再重新看下代码,唯一能优化的只有这一段

for(int l=2;l<j;l++){
    min = Math.min(min, Math.max(dp[i-1][l-1]+1, dp[i][j-l]+1));
}

如果把dp[i][x]看成一个函数的话,他肯定是单调递增的,那么dp[i][j-x]必定单调减。也就是说这俩的交点,正好是我们要的答案。
但是这函数不是连续的,我们要的点可能不是一个整数,那最小值就是左右两端的点了,直接用二分法去找。在重合点左边的时候dp[i][x]是小于dp[i][j-x]的,那么每次取中点,判断他的状态,如果在左边更新左边的点,反之更新右边的点,最后取到左右点,取出最小值。
代码如下:

public int superEggDrop(int k, int n) {
        if(k==1){
            return n;
        }
        int[][] dp = new int[k+1][n+1];
        for(int i=1;i<n+1;i++){
            dp[1][i] = i;
        }
        for(int i=2;i<k+1;i++){
            for(int j=1;j<n+1;j++){
                int min = dp[i-1][j];
                int left=2,right=j;
                while(left<right){
                    int center = (left+right)/2;
                    if(center==left){
                        min = Math.min(min, Math.max(dp[i-1][center-1]+1, dp[i][j-center]+1));
                        min = Math.min(min, Math.max(dp[i-1][right-1]+1, dp[i][j-right]+1));
                        break;
                    }
                    boolean centerB = dp[i-1][center-1]<dp[i][j-center];
                    if(centerB){
                        left = center;
                    }else{
                        right = center;
                    }
                }
                dp[i][j] = min;
            }
        }
        return dp[k][n];
    }
posted @ 2022-01-18 17:33  阿飞飞啊飞  阅读(40)  评论(0编辑  收藏  举报