今天在知乎上看到一道有趣的思考题,它的来源是leetcode的458题,题目引入如下:
有 1000 只水桶,其中有且只有一桶装的含有毒药,其余装的都是水。它们从外观看起来都一样。如果小猪喝了毒药,它会在 15 分钟内死去。 问题来了,如果需要你在一小时内,弄清楚哪只水桶含有毒药,你最少需要多少只猪? |
这道题代码本身并无技术含量,关键在于怎样设计一个测毒算法,其中要求猪猪🐖的数目最少,同时还包含了时间限制。
那应该怎样去思考这道题呢?我建议大家都能想一想再看后面的解析。
这道题首先让我想起的是小的时候看见的一道秤坏果问题:
| 有10个苹果,其中有一个坏果,坏果的质量轻于其他正常的苹果,问:使用天平进行测量,至多需要测量几次就能找到坏果? |
答案是:两次三次。
第一次:左右两边各5个苹果。
第二次:左右两边各2个苹果。若相等,则另外一个苹果是坏果。若不相等,则用手掂量掂量就知道哪个是坏果(所以正确答案应该是不用秤)
第三次:再比较两果。(答案不唯一)

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

该怎么思考呢?
我们知道,猪开始执行验毒任务后,它只会有两种状态,死了,或者活着。
我们定义死了为1,活着为0。将水桶编号为0~999。

0~999对应着1000个二进制数字。
而我们知道2^10=1024>1000。所以我们需要10头猪,就能一次测出毒水。
操作方法如下:
让第一头猪喝所有二进制第一位表示为1开头的水。
让第二头猪喝所有二进制第二位表示为1开头的水。
………………
让第九头猪喝所有二进制第九位表示为1开头的水。
让第十头猪喝所有二进制第十位表示为1开头的水。
猪死了,那该位记为1,猪没死,那该位记为0

假设最后得到了上面的结果,六头猪猪壮烈牺牲,最后我们就确定出
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

这大概是猪最幸运的情况……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)的算法。
根据数学推导,可以推出以下关键式:

代码表示为:
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)具体已经贴到文章末尾的阅读原文中。
1463

被折叠的 条评论
为什么被折叠?



