【Leetcode】458. Poor Pigs(配数学证明)

题目地址:

https://leetcode.com/problems/poor-pigs/

(本文配了对题目解答的严格数学证明,不仅证明了至少所需要小猪数量的上界,也证明了下界。题目问的是最少需要的小猪数量是多少,所以解的正确性和最优性都必须要证明。本文对最优性也给予了严格证明。)

给定一列桶,其中有且仅有一只桶里面是毒药,其余都是普通的水。如果一个小猪喝了毒水,过一段fixed的时间会毒死(在这段时间里不能喝别的水,只能观察)。假设小猪每次可以同时喝很多桶水,并且每次小猪们如果喝水也都是同时喝。给定一段时间,问多少只小猪可以保证在这段时间里验证出哪只桶有毒。

这道题实际上是一道编码理论的题。我们先证明一个引理:
设有一列数字 0 , 1 , 2 , 3 , . . . , n − 1 0,1,2,3,...,n-1 0,1,2,3,...,n1,一共 n n n个数字;假设有一个人甲,心中想了一个在这个范围内的数字,另一个人乙想猜出甲心中想的是什么数字,但乙只能问形如 k k k 1 1 1的选择题,并且有且仅有一个选项是对的。问乙至少需要问多少次才能保证必然能猜出甲心中想的数字。

假设乙至少要问 c c c次就能猜出。我们先考虑 c c c的上界。将 0 , 1 , 2 , . . . , n − 1 0,1,2,...,n-1 0,1,2,...,n1 n n n个数字转换为 k k k进制整数,那么最大的数 n − 1 n-1 n1 ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1位(这里多少位的意思是,写成最高位非 0 0 0 k k k进制数后,有多少位)。

我们先证明这个小结论。设 m m m k k k进制下有 x x x位,那么必然有 k x − 1 ≤ m ≤ k x − 1 k^{x-1}\le m\le k^x-1 kx1mkx1(因为 x x x k k k进制的最小数是一个 1 1 1后面跟 x − 1 x-1 x1 0 0 0,也就是 1000... = k x − 1 1000...=k^{x-1} 1000...=kx1,最大数是 x x x 1 1 1,也就是 111... = k x − 1 111...=k^x-1 111...=kx1),所以有 log ⁡ k ( m + 1 ) ≤ x ≤ log ⁡ k m + 1 \log_k(m+1)\le x\le \log_k m+1 logk(m+1)xlogkm+1由于 x x x是整数,所以 x ≤ ⌊ log ⁡ k m ⌋ + 1 x\le \lfloor\log_k m\rfloor + 1 xlogkm+1,而又因为 x x x是存在且唯一的,所以 x = ⌊ log ⁡ k m ⌋ + 1 x= \lfloor\log_k m\rfloor + 1 x=logkm+1(这里的逻辑是,既然 x x x是满足 k x − 1 ≤ m ≤ k x − 1 k^{x-1}\le m\le k^x-1 kx1mkx1的唯一整数,又有这个不等式 log ⁡ k ( m + 1 ) ≤ x ≤ log ⁡ k m + 1 \log_k(m+1)\le x\le \log_k m+1 logk(m+1)xlogkm+1,而 ⌊ log ⁡ k m ⌋ + 1 \lfloor\log_k m\rfloor + 1 logkm+1这个数代入 x x x是满足不等式的,所以 x x x就等于它),证毕。

接下来就可以求 c c c到底是多少了:
步骤1:证明 c ≤ ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 c\le \lfloor\log_k(n-1)\rfloor+1 clogk(n1)+1。只需证明存在这样一种问法,每次问 k k k 1 1 1的选择题,经过不等号右边这么多次,一定能问出甲心里想的数字即可。问题设计如下:
第一个问题,甲心中所想之数在 k k k进制下右起第 1 1 1位数是 0 , 1 , . . . , k − 1 0,1,...,k-1 0,1,...,k1中的哪一个;
第二个问题,甲心中所想之数在 k k k进制下右起第 2 2 2位数是 0 , 1 , . . . , k − 1 0,1,...,k-1 0,1,...,k1中的哪一个;
第三个问题,甲心中所想之数在 k k k进制下右起第 3 3 3位数是 0 , 1 , . . . , k − 1 0,1,...,k-1 0,1,...,k1中的哪一个;

⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1个问题,甲心中所想之数在 k k k进制下右起第 ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1位数是 0 , 1 , . . . , k − 1 0,1,...,k-1 0,1,...,k1中的哪一个;
一共 ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1个问题,每个问题都是 k k k 1 1 1的选择题,并且答案一定存在选项里。很显然这么多问题全得到答案后,乙就知道了甲想的是什么数了。

步骤2:证明 c ≥ ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 c\ge \lfloor\log_k(n-1)\rfloor+1 clogk(n1)+1。反证法。如果存在少于 ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1个数个问题,比如只问 l l l个问题,也能问出甲心中所想的数,那么我们按照每个问题,对 0 ∼ n − 1 0\sim n-1 0n1中的数编码,对于每个数右起第一位数,我们让它等于第一个问题答案的选项编号(从 0 0 0开始)。例如,如果在第一个问题下,数字 i i i对应的正确选项是第 j j j项,那么我们就让 i i i的右起第一位数是 j j j。以此类推。这样, 0 ∼ n − 1 0\sim n-1 0n1中的每个数都映射到了一个 l l l进制数。由于 ⌊ log ⁡ k ( n − 1 ) ⌋ \lfloor\log_k(n-1)\rfloor logk(n1) l l l进制下最大的数仍然是小于 n n n的,根据抽屉原理,这就意味着这 n n n个数必然存在两个数,它们被映射到了同一个 l l l进制数。假设甲心里想的就是那两个数其中一个,那么乙在问完之后是无法区分是那两个数的哪一个的(因为这两个数在乙的问题下,每道题答案都一模一样,乙无法判断是哪个),这就矛盾了。所以乙如果只问少于 ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1个问题,是不能保证问出结果的。

综上,所以有: c = ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 c=\lfloor\log_k(n-1)\rfloor+1 c=logk(n1)+1整个证明过程实际上就是在考虑这样一个问题:如何编码,才能保证解码的时候可以原样还原。

接下来我们就可以解决这道题了。我们把每个桶映射到 0 ∼ n − 1 0\sim n - 1 0n1这些数上去。一只小猪去喝水,它最多可以喝 m i n u t e s T o T e s t m i n u t e s T o D i e \frac{minutesToTest} {minutesToDie} minutesToDieminutesToTest这么多次。令 m i n u t e s T o T e s t m i n u t e s T o D i e + 1 = k \frac{minutesToTest} {minutesToDie}+1=k minutesToDieminutesToTest+1=k,每次喝水的时候就相当于在给每个桶编码。具体可以这样做,先让第一只小猪去喝所有 k k k进制下个位数为 0 0 0的桶,第二只小猪去喝所有 k k k进制下十位数为 0 0 0的桶,这样以此类推。过了第一段时间,如果某只小猪死了,我们就知道了有毒的桶在 k k k进制下的某位数是 0 0 0了,如果某只没死,就让它接着喝本位数为 1 1 1的桶。一共最多需要喝 k − 1 k-1 k1次(之所以是 k − 1 k-1 k1,是因为如果小猪喝了 k − 1 k-1 k1次还没死,说明本位数就是 k − 1 k-1 k1,并不需要多喝一次才能判断),我们就能知道有毒水的每位数字是几了。所以所需要的小猪数量就是 n − 1 n-1 n1 k k k进制下的位数。

用上面的引理来理解就是,每只小猪相当于一个问题,而 m i n u t e s T o T e s t m i n u t e s T o D i e + 1 \frac{minutesToTest} {minutesToDie}+1 minutesToDieminutesToTest+1相当于选项数。至少要问多少问题才能得到答案。

代码如下:

public class Solution {
    public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
        int k = minutesToTest / minutesToDie + 1;
        int n = buckets - 1;
        int count = 0;
        while (n != 0) {
            n /= k;
            count++;
        }
        return count;
    }
}

问题还没有结束。上面只是证明了有那么多只小猪就能保证得到答案,但如何才能证明最少就需要这么多小猪呢?我们只是得到了上界,还没有完全证明出这个上界就等于下界。其实下界可以由那个引理得到。证明如下:

每只小猪喝水,一共喝了 k − 1 k-1 k1轮,比如说,第一只小猪,假设每次喝的桶的集合是 A 1 , A 2 , . . . , A k − 1 A_1,A_2,...,A_{k-1} A1,A2,...,Ak1这相当于在问一个 k k k选题,即有毒的水在 A 1 , A 2 , . . . , A k − 1 , C A_1,A_2,...,A_{k-1},C A1,A2,...,Ak1,C(其中 C C C是其余集合并集的补集)之一吗?每只小猪事实上都相当于在问形如这样的选择题。由前面的引理,要问出结果,至少需要 ⌊ log ⁡ k ( n − 1 ) ⌋ + 1 \lfloor\log_k(n-1)\rfloor+1 logk(n1)+1这么多次问题才行,这就是所需要的小猪的数量。证明完毕。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值