一道有趣的思考题:poor pig

今天在知乎上看到一道有趣的思考题,它的来源是leetcode的458题,题目引入如下:

有 1000 只水桶,其中有且只有一桶装的含有毒药,其余装的都是水。它们从外观看起来都一样。如果小猪喝了毒药,它会在 15 分钟内死去。

问题来了,如果需要你在一小时内,弄清楚哪只水桶含有毒药,你最少需要多少只猪?

这道题代码本身并无技术含量,关键在于怎样设计一个测毒算法,其中要求猪猪🐖的数目最少,同时还包含了时间限制。

那应该怎样去思考这道题呢?我建议大家都能想一想再看后面的解析。

这道题首先让我想起的是小的时候看见的一道秤坏果问题:

有10个苹果,其中有一个坏果,坏果的质量轻于其他正常的苹果,问:使用天平进行测量,至多需要测量几次就能找到坏果?

答案是:两次三次。

第一次:左右两边各5个苹果。

第二次:左右两边各2个苹果。若相等,则另外一个苹果是坏果。若不相等,则用手掂量掂量就知道哪个是坏果(所以正确答案应该是不用秤)

第三次:再比较两果。(答案不唯一)

outside_default.png

但是,今天这一题显然不一样啊,秤苹果天平不会坏,但猪猪可是会死的!猪猪保护协会强烈谴责。所以为了减少紧张的猪的数量,我们必须选用最少数目的猪完成今天的任务,虽然可能会死得更多。

outside_default.png

该怎么思考呢?

我们知道,猪开始执行验毒任务后,它只会有两种状态,死了,或者活着。

我们定义死了为1,活着为0。将水桶编号为0~999。

outside_default.png

0~999对应着1000个二进制数字。

而我们知道2^10=1024>1000。所以我们需要10头猪,就能一次测出毒水。

操作方法如下:

让第一头猪喝所有二进制第一位表示为1开头的水。

让第二头猪喝所有二进制第二位表示为1开头的水。

………………

让第九头猪喝所有二进制第九位表示为1开头的水。

让第十头猪喝所有二进制第十位表示为1开头的水。

猪死了,那该位记为1,猪没死,那该位记为0

outside_default.png

假设最后得到了上面的结果,六头猪猪壮烈牺牲,最后我们就确定出

1010010111(2)=663(10)第663编号的水为毒水。

假设手速极快,我们15分钟就可以得到结果。

以此类推,假如我们增加测试次数,怎样减少猪的头数呢?

由题目,我们可以测量四次。

于是,容易自然而然的想到 6^4=1296,从而你会说,这简单啊,那6头猪不久好了?而得到一个错误的答案,在上面的天平坏果问题当中,有一个有趣的地方在于我们第二次测,有一个果子不需要测就可以判断它是否是坏果,这是排除法的应用。因此,测四次,我们可以判定五个结果!!就像二进制测一次判定了两次结果一样!!没必要消灭应该活下来的猪猪!!虽然4^5=1024能表示1024桶水但是由于4进制的限制4位数的4进制只能表示256位数,故只能选择5^5次方。最终我们用5头猪就可以在一个小时内完成验毒任务。

操作方法如下:

第一次:

第一头喝第一位是0的水;

第二头喝第二位是0的水;

…………

第五头喝第五位是0的水;

第二次到第五次分别喝1,2,3的水。

喝到几猪死,位数就记为几,猪幸运的没死,根据排除法就记为4

outside_default.png

这大概是猪最幸运的情况……04444(5)=624(10)判断出第624桶水是毒水。

理解了以上的内容,再看进阶的leetcode考题就十分简单啦


进阶:假设有 n 只水桶,猪饮水中毒后会在 m 分钟内死亡,你需要多少猪(x)就能在 p 分钟内找出 “有毒” 水桶?这 n 只水桶里有且仅有一只有毒的桶。

我们根据以上的推理可以知道:

① x的p/m+1次方应该要大于n,即猪应该覆盖所有数字

② x应该大于或等于p/m+1,即猪数目的进制应该能表示所有桶

满足这两个条件的x就可以求出来啦。

for (int x = 1; x <= buckets; x++)
    {
        p = minutesToTest / minutesToDie + 1;
        if (x >= p && pow(x, p) > buckets)
        {
            cout << x;
            break;
        }

不过这并不是最优题解。他的时间复杂度为O(N)

利用信息论有关的知识可以写出时间复杂度为O(1)的算法。

根据数学推导,可以推出以下关键式:

outside_default.png

代码表示为:

C++JavaPythonclass Solution {
public:
    int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
        int states = minutesToTest / minutesToDie + 1;
        return ceil(log(buckets) / log(states));
    }
};


作者:LeetCode
链接:https://leetcode-cn.com/problems/poor-pigs/solution/ke-lian-de-xiao-zhu-by-leetcode/
来源:力扣(LeetCode)

具体已经贴到文章末尾的阅读原文中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值