题目描述:
有 buckets 桶液体,其中 正好有一桶 含有毒药,其余装的都是水。它们从外观看起来都一样。为了弄清楚哪只水桶含有毒药,你可以喂一些猪喝,通过观察猪是否会死进行判断。不幸的是,你只有 minutesToTest 分钟时间来确定哪桶液体是有毒的。
喂猪的规则如下:
选择若干活猪进行喂养
可以允许小猪同时饮用任意数量的桶中的水,并且该过程不需要时间。
小猪喝完水后,必须有 minutesToDie 分钟的冷却时间。在这段时间里,你只能观察,而不允许继续喂猪。
过了 minutesToDie 分钟后,所有喝到毒药的猪都会死去,其他所有猪都会活下来。
重复这一过程,直到时间用完。
给你桶的数目 buckets ,minutesToDie 和 minutesToTest ,返回 在规定时间内判断哪个桶有毒所需的 最小 猪数
解题方案1:
思路:
标签:数学
这道题初看的时候,很多人会纠结:到底需要多少只小猪,而每只小猪又应该具体如何喝水才能判断出哪只水桶有***?
这道题最开始不要去关注细节,去想到底应该怎么喂水。而是应该先思考在考察哪方面的问题,数组、链表、二叉树还是数学?那么仔细思考就能得出结论,本质上在考察数学中的 进制 问题。
举例说明:
假设:总时间 minutesToTest = 60,死亡时间 minutesToDie = 15,pow(x, y) 表示 x 的 y 次方,ceil(x)表示 x 向上取整
当前有 11 只小猪,最多可以喝 times = minutesToTest / minutesToDie = 4 次水
最多可以喝 44 次水,能够携带 base = times + 1 = 5 个的信息量,也就是(便于理解从 00 开始):
(1) 喝 00 号死去,00 号桶水有毒
(2) 喝 11 号死去,11 号桶水有毒
(3) 喝 22 号死去,22 号桶水有毒
(4) 喝 33 号死去,33 号桶水有毒
(5) 喝了上述所有水依然活蹦乱跳,44 号桶水有毒
结论是 11 只小猪最多能够验证 55 桶水中哪只水桶含有***,当 buckets ≤ 5 时,answer = 1
那么 22 只小猪可以验证的范围最多到多少呢?我们把每只小猪携带的信息量看成是 base进制数,22 只小猪的信息量就是 pow(base, 2) = pow(5, 2) = 25,所以当 5 ≤ buckets ≤ 25时,anwser = 2
那么可以得到公式关系:pow(base, ans) ≥ buckets,取对数后即为:ans ≥ log(buckets) / log(base),因为 ans 为整数,所以 ans = ceil(log(buckets) / log(base))
链接:https://leetcode-cn.com/problems/poor-pigs/solution/hua-jie-suan-fa-458-ke-lian-de-xiao-zhu-by-guanpen/
class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
//1.假设含有***的水是随机事件 X
//2.随机小猪死亡的时间段是随机时间Y
float infoX = log(buckets);// 寻找毒水总的信息量
int time = minutesToTest / minutesToDie +1;//在规定时间内猪的状态数(假设minutesToDie=15,minutesToTest=60,猪可以喝四次水,可以在四个时间段死去,以及不死,总共5种状态)
float infoY = log(time); //一只猪提供的信息量
int ans = ceil(infoX / infoY);//
return ans;
}
};
c++代码2:
用猪猪的生死状态:
- 一只猪在一次观察中,他的状态只有两种,活着或者死了。此时可以判断两桶水中哪一桶有毒。
- n只猪在一次观察中,他们的状态有2^n种。此时可以判断2^n桶中哪一桶有毒。具体方式为:将各个桶用2进制编码,第i只猪喝去喝所有[二进制编码中第i位为1的]桶,如果第1、3、4猪死了,说明二进制为0000001101的水桶有毒。
- 一只猪在k次观察中,他的状态有k+1种:第一次就死了、第二次就死了...第k次才死、最后还活着。此时可以判断k+1桶水中哪个有毒。具体地,用k+1进制将水桶编码,第一次喝第0桶,第k次喝第k-1桶,如果最后没死就是第k桶有毒。
- n只猪在k次观察中,他们的状态共有(k+1)^n种,则最多可以判断这么多桶水中哪个有毒。
class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
if (buckets == 1) return 0;
int runs = minutesToTest / minutesToDie; // 多少次观察
int status = runs + 1; // 一只猪有多少种状态
int pigs = 1;
int covered = status; // 可以判断多少桶水
while (covered < buckets) {
// 如果猪不够用,则增加一只猪,同时能够判断的水桶变成加猪之前的status倍
covered *= status;
pigs++;
}
return pigs;
}
};
代码3:本质其实就是一个数字进制表示的问题.
先来考虑一种最简单的情况,buckets = 1000, minutesToDie = 15, minutesToTest = 15,此时每只小猪都只能喝1次液体(再喝时间就不够了),每个小猪喝完若干瓶液体后只能出现两种状态,要么死亡,要么存活.
我们给这 1000 瓶液体分别标上一个唯一编号 0-999,由于 2^10 > 1000 >2^9
,所以每瓶液体都对应着唯一的一个长度为10的二进制串. 我们只需要10只小猪,让每个小猪负责一个二进制位即可. 例如第一只小猪负责二进制串的最低位,那么它就需要喝掉所有二进制最低位为1的液体,如果这只小猪最后死亡,说明有毒液体的编号二进制最低位为1;否则小猪存活,有毒液体的编号二进制最低位为0. 这样一来,一只小猪就可以确定一个二进制位的取值,使用10只小猪就能完全确定有毒液体的编号.
来考虑更一般的情况,buckets = 1000, minutesToDie = 15, minutesToTest = 60,此时每只小猪可以喝4次液体,在时间限制范围内,小猪可能出现的状态共有5种,分别为:喝完第1次后死亡、喝完第2次后死亡、喝完第3次后死亡、喝完第4次后死亡、喝完4次后依然存活. 现在每只小猪可以表示5种状态了,而不是之前的2种,那么我们就可以将瓶子的编号转换成五进制数考虑.
我们依然给这 1000 瓶液体分别标上一个唯一编号 0-999,由于 5^5>1000>5^4
,所以每瓶液体都对应着唯一的一个长度为5的五进制串. 我们只需要5只小猪,让每个小猪负责一个五进制位即可. 例如第一只小猪负责五进制串的最低位,那么它第一次先喝掉五进制最低位为1的液体,第二次喝掉五进制最低位为2的液体,第三次喝掉五进制最低位为3的液体,第四次喝掉五进制最低位为4的液体. 在这一过程中,如果这只小猪某次喝完后死亡,就可以立马确定有毒液体五进制的最低位取值,如果喝完四次后仍存活,说明有毒液体五进制的最低位为0. 这样一来,一只小猪就可以确定一个五进制位的取值,使用5只小猪就能完全确定有毒液体的编号.
将问题的解抽象成更一般的数学公式如下:
base =( minutesToTest/minutesToDie )+1
base^ans >=buckets
base
表示对应的是几进制数,将第二个式子左右两端同时取对数,即可得到答案:
class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
int base=minutesToTest/minutesToDie+1;
return ceil(log(buckets)/log(base));
}
};
链接:https://leetcode-cn.com/problems/poor-pigs/solution/leetcode-458-njin-zhi-si-xiang-by-xiaok0-819a/
python代码:
class Solution:
def poorPigs(self, buckets: int, minutesToDie: int, minutesToTest: int) -> int:
return ceil(log(buckets, minutesToTest//minutesToDie + 1))
。
解决方法2:动态规划
参考官网链接:
链接:https://leetcode-cn.com/problems/poor-pigs/solution/ke-lian-de-xiao-zhu-by-leetcode-solution-z0h7/
该题核心代码:
return ceil(log(buckets)/log(minutesToTest/minutesToDie+1));
}