扔鸡蛋问题-动态规划

本文介绍了如何使用动态规划解决扔鸡蛋问题,通过分析不同数量的鸡蛋和楼层,探讨最优策略以最少尝试次数找到鸡蛋不会破裂的最高楼层。文章指出,关键在于递归表达式和确定基础情况,最后讨论了二维数组实现的效率问题。
摘要由CSDN通过智能技术生成

这个问题是一个印度朋友问我的。

已知有e个鸡蛋,f层楼。问题是尽可能少的尝试,找出扔出鸡蛋不破的最高的楼层。

最开始我的思路是,如果只有一个鸡蛋,f层楼,毫无疑问,只能从一楼到二楼到三楼...依次尝试,这样f层楼最坏的情况下要进行f次尝试。为什么要考虑最坏的情况,因为我们要确保该结果包含所有可能的情况,下面再详细说。然后我分析两个鸡蛋,f层楼。然后我发现,如果有两个鸡蛋,就可以从第2层楼开始扔:如果破了,在用剩下的1个鸡蛋检查1楼;如果没破,两个鸡蛋升至4楼检查同理。如果有3个鸡蛋,f层楼,可以从4楼开始检查:如果破了,2个鸡蛋退至2楼检查同上;如果没破,3个鸡蛋升至8楼检查同理。这样我能确保在耗尽所有鸡蛋的同时,发现最高的楼层。然后开始选择楼层是2的e-1次方,e是鸡蛋的数量,如果不破,继续上升2的e-1次方,破了下降2的e-2次方。。。虽然麻烦点儿,但是理论可行,代码实现也没有问题。

然后我很高兴的告诉印度朋友我解决了这个问题。印度朋友听完我的解释后说跟标准答案不一样。后来一经分析,他指出了这个方法的破绽,我太天真,这个方法是没问题,可以发现扔鸡蛋不破的最高楼层,但并不是最少的尝试。

然后告诉我这个题是有关动态规划。动态规划就是大量递归,但是存在重复计算,因此在开始递归的时候把结果保存在表里面,这样下次用到直接从表里面找不用再次计算,稍微快一点。

然后了解的官方的解释。假设有f层楼,e个鸡蛋。我们用一个表达式E(f, e)来表示这个扔鸡蛋问题的最优答案,即:在有n个鸡蛋f层楼的情况下,找出扔鸡蛋不破的最高楼层的最少尝试次数。然后开始考虑,首先从那一层楼开始扔,结果最佳?前面我尝试过从2的e-1次方层开始仍,但印度老哥已经证明这个方法有问题。所以,该从哪里扔?答案是不知道,至少我不知道。但是我知道计算机速度很快,我们完全可以枚举出所有的可能,然后从里面找出最优解。那就行了,假设先从k层楼往下扔,答案是多少?首先来到k层,扔出去鸡蛋,进行了1次尝试。然后需注意,扔出去鸡蛋的时候,会产生两种结果,一种是鸡蛋破了,一种鸡蛋没破。这两种情况实际只有一种情况会发生。但是我们应该考虑最坏的情况,也就是上界,确保这个上界能够cover两种情况。首先鸡蛋破了,我们知道还剩下e-1个鸡蛋,还知道最高的楼层肯定在k层以下,但不知道就那一层,所以这个时候用E(k-1, e-1)来表示后面最后的操作。如果没破,知道手里还有e个鸡蛋,而且最高楼层肯定在k层以上,只需检查以上的楼层,那就是E(f-k, e)来表示后面的最后操作。所以在k层开始仍的最优解是1+max(E(k-1, e-1), E(f-k, e))。然后因为我们有f层,所以k的值就是从1到f,也就是我们在先从那层楼开始仍这一问题上有f个选择,都可以用递归表达式表示出来。然后我们要做的就是从里面找出最小值,就是E(f, e)的最终答案。

熟悉递归的朋友肯定知道,我们必须知道base case的具体数,不然没法用递归。这里的base case有两个:如果有1个鸡蛋,f层楼,结果会是多少?是f;如果有e个鸡蛋1层楼,结果是多少?是1。然后就行了,有了base case,有了递归表达式,这个问题就解决了。

然后我是用了一个二维数组做记录,比纯递归能快多少没研究,不过总体效率不高。以后有空会去看一下官方的解法,目前没兴趣。基本跟二叉树的写法差不多。印度老哥号称深谙这“简单的 ”二叉树,结果连这个递归写不出来。不懂装懂的人还是挺多。

public class EggDrop
{
    private int[][] resultSet;
    private int floor;
    private int egg;

    public EggDrop(int floor, int egg)
    {
        resultSet = new int[floor+1][egg+1];
        this.floor = floor;
        this.egg = egg;
        for(int i = 0; i < egg+1; i++)
        {
            resultSet[0][i] = 0;
            resultSet[1][i] = 1;
        }
        for(int j = 0; j < floor+1; j++)
        {
            resultSet[j][0] = 0;
            resultSet[j][1] = j;
        }
    }

    public int minTry() { return minTry(floor, egg); }

    private int minTry(int floor, int egg)
    {
        if(floor == 0 || egg == 0 || resultSet[floor][egg] != 0)
            return resultSet[floor][egg];

        int min = Integer.MAX_VALUE;

        for(int i = 0; i < floor; i++)
        {
            int temp = 1 + Math.max(minTry(floor-i-1, egg-1), minTry(i, egg));
            if(temp < min) min = temp;
        }

        resultSet[floor][egg] = min;

        return min;
    }

    public static void main(String[] args)
    {
        int floor = 20;
        int egg = 8;
        EggDrop eggDrop = new EggDrop(floor, egg);
        System.out.println(eggDrop.minTry());
        int count = 0;
        for(int i = 0; i < floor+1; i++)
        {
            for(int j = 0; j < egg+1; j++)
            {
                if(count < egg)
                {
                    count++;
                    System.out.print(eggDrop.resultSet[i][j] + " ");
                }
                else
                    {
                        System.out.println(eggDrop.resultSet[i][j]);
                        count = 0;
                    }
            }
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值