前言
动态规划是大家都熟悉与陌生的知识,非常灵活多变,我自己也不敢说自己掌握了,今天给大家介绍一道题,不仅局限于动态规划做题,还会涉及到信息论,并探讨自己认知世界的角度 因为比较难,本文不会详细介绍动态规划方法,所以需要读者有一定基础,否则可能理解有困难
题目链接:可怜的小猪
有buckets
桶液体,其中 正好有一桶 含有毒药,其余装的都是水。它们从外观看起来都一样。为了弄清楚哪只水桶含有毒药,你可以喂一些猪喝,通过观察猪是否会死进行判断。不幸的是,你只有 minutesToTest
分钟时间来确定哪桶液体是有毒的。
喂猪的规则如下:
- 选择若干活猪进行喂养
- 可以允许小猪同时饮用任意数量的桶中的水,并且该过程不需要时间。
- 小猪喝完水后,必须有
minutesToDie
分钟的冷却时间。在这段时间里,你只能观察,而不允许继续喂猪。 - 过了
minutesToDie
分钟后,所有喝到毒药的猪都会死去,其他所有猪都会活下来。 - 重复这一过程,直到时间用完。
给你桶的数目 buckets
,minutesToDie
和 minutesToTest
,返回 在规定时间内判断哪个桶有毒所需的 最小 猪数 。
示例 1:
输入: buckets = 1000, minutesToDie = 15, minutesToTest = 60
输出: 5
示例 2:
输入: buckets = 4, minutesToDie = 15, minutesToTest = 15
输出: 2
示例 3:
输入: buckets = 4, minutesToDie = 15, minutesToTest = 30
输出: 2
解法
dp解析
一般来说,动态规划就是三步骤
推到最后,大部分情况下,dp数组最后一个值就是答案
这个题,
题中有buckets,minutesToTest,minutesToDie三个变量,由于正面角度比较难思考,可以反过来, 全问题为n只小猪,能够有限的轮次测试中测出毒药,一轮耗时minutesToDie,总轮数为minutesToTest/minutesToDie
所以,其子问题为i只小猪测试j轮的结果,不影响后续测试,而后续测试需要子问题的结果来递推 满足动态规划的重叠子问题和无后效性原则,同时又是求最值(简称n),所以确定可以用动态规划(简称dp)
-
首先定义数组含义 f(i, j)表示i只小猪,测试j轮后最多可以在多少butkets中找到毒药,显然,这是一个二维数组dp[i] [j]
-
然后赋予初始值 dp[0] , dp[i]都为1,相当于没有测试,因为知道必有一桶毒,所以bucket = 1时,可以肯定为有毒,dp[0]没有猪,也全为1
-
状态转移 假设现在状态要算f(i, j),一轮测试后还剩下k只猪存活,而测试剩下j - 1次,可以确定,f(k, j - 1)就是f(i, j )的前一个状态,并将递推出f(i, j) 从i变为k的可能组合数为 C(i, k),因此f(i, j) = C(i, k) * f(k, j - 1),其中k的取值为0~i,所以最后的计算如下
到了这步,已经掌握了解题钥匙,更多细节可以参考题解, 本文并非想详细介绍题解,更多的是探讨思想
信息论
抛开dp的过程,只看开始和结尾,buckets,minutesToTest,minutesToDie都限定后,通过递推或者某种方式,我们就能得到最少的猪数量n,也就是说,当相关信息量确认好后,答案就确定了
这给我们一个很大提示,那就是,面对一个问题的时候,在耗费时间去做之前,仅仅凭借已经掌握的信息,就能判断出能不做成。注意,这里不是靠经验或者直觉,而是真真实实的科学,若是吸收这一思想,并勤加练习,相当多的难题可以得到解决
那现在我们就来仔细了解下这一理论吧
我们知道,计算机的很多数据看不见摸不着,我们将其统一设置为二进制,使用bit来记录数据,创造纷繁的信息世界。而我们,不知道三维世界的造物主用的几进制,在这个世界的我们无法用自己的信息来精确表达信息本身
熵
不过没关系,我们可以一个模糊的“熵”暂时代替,表示混乱度,越混乱,熵越高
熵增定律:在一个孤立系统里,如果没有外力做功,其总混乱度(熵)会不断增大
对于理科生很好理解,毕竟学过 对于保洁也很好理解,毕竟一个房间如果长期不打扫,肯定会变脏 对于老师也很好理解,毕竟他如果长时间不来教室,教室肯定乱套 对于宅男更好理解,长期不外出不和人交流,一定……
信息熵与信息
在信息论中,信息熵表示信息的不确定程度
比如,我们都知道,同样的内容,中文写的往往比英文薄一些,实际上,就是因为中文的信息不确定程度高,也就是信息熵大,所以所需的信息量就会少,字数也会少,往往掌握1000个汉字就能应付日常说话,很多汉字在不同的组词下有大量不同的含义。而英文,掌握5000单词都未必能说清楚
但是信息熵大未必是好事,信息熵越大,不确定程度越高,其实代表信息量越少,而减少信息熵的方式,就是增加含信息量的信息,不含信息量的信息,可以归类为废话(说到这,我都不认识信息两字了)
所以方向来了!对于一件富含信息熵的事情,我只要掌握足够信息量的信息,与信息熵等价,那么就可以做!
比如,明天是否下雨?,这件事信息熵很大,天气预报的结果,天上的乌云,蚂蚁搬家等等都是多多少少降低信息熵的信息
到了这里,你可以还是会有疑惑,说来说去,最后不还是靠感觉吗?不过是用了一些科学名词归类而已
香农厉害就厉害在这里,讲一个千万年来人说不清的感觉提取出公式,让人类的认知进入了一个全新领域。在信息论提出之后,个人认为能与这一瞬间媲美的只有阿姆斯特朗的那一小步。 公式如下
看起来挺复杂,但是理解很简单,
其中,H(X)表示随机变量X的信息熵,P(xi)表示X取值为xi的概率,logb表示以b为底的对数。
这里的b表示信息单位的基础,如果你想二进制表示,那么b == 2 x表示事件,xi可以理解为x里面的某种元素,公式就是将X事件中,1~n种元素发生的概率求和而来,n可以理解为可能的所有情况
比如扔一枚硬币,只有两种情况,那么其信息熵为
也就是说,只要有一个含1bit信息量的信息,就可以消灭信息熵,比如我告诉你,硬币为正面,此时,信息熵消失
解题
回到小猪的话题,题目中H(X)信息熵,在minutesToTest,minutesToDie限定下,x为bucket数,x为1000时
解释:1/1000表示任何桶水都有1/1000可能是毒水,然后有1000种可能性,需要累加
我们可以对这个结果有些感性认识,看起来信息熵的增长不是线性的,而是log,扔硬币这种五五开的事情为1,而1000桶水找1个有毒居然只有9.97,当有10000桶水时,信息熵为log2(10000) = 13.29
现在我们考虑:多少小猪能够提供大于9.97的信息熵?从示例一中
输入:buckets = 1000, minutesToDie = 15, minutesToTest = 60
输出:5
看出,可以测试 60 / 15 = 4轮,那么共计有5种状态,那就是4种死亡的场景 + 1种存活场景,在这一情况下,“1只小猪的5种状态”可以提供的信息熵为
解释:1/5表示小猪等可能进入到5种状态中的一种,有5次统计,需要累加
一只小猪是这么多,那么2只猪呢?会产生 5^2 = 25种可能性,3只就会有125种可能性,计算可得
所以
对于示例一,需要至少4.3头猪才能提供答案
当buckets变化时,原题的解题代码为
class Solution {
public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
int times = minutesToTest / minutesToDie + 1;
double time = Math.log(buckets) / Math.log(times); // 信息熵相除
return (int)Math.ceil(time);
}
}
后记
信息论现在广泛运用在了压缩编码,通信协议和设计等的底层算法理论,相等于给后人研究算法提供了一个天花板,只要你的算法结果没达到信息论理论极限,就有提升空间,要是超过了,那你肯定是错的
当我们重新审视这一解法,除了感到些许震撼之外,难免会有疑问,比如,
- 你可能好奇小猪如何喝水才能在5只的情况下测1000桶水
可以参考另一篇文章: 小兔喝水
- 为什么小猪算出来的信息熵可以和水的信息熵等价相除?
显然,信息熵就是信息熵,他自身是可以比较的,并不存在什么xx的信息熵。很多东西也不存在什么中西之分,只有真假之分
所以,我们应该更加关注的,是跳脱出事情的表面属性,去提取出本质的信息量,生活中无法严格按照公式去计算,但是,当我们做一些比较重要的决策时,可以再想想,我是否已经掌握了足够的信息?
什么叫足够的信息?
比如选取高考志愿,在我看来至少需要的信息有:目标专业知识与未来前景,目标大学的地理位置与名气,自己的喜好等等,更加深层次的由于我并不精通信息论专业,但是希望你读完有所收获~
参考链接:
www.zhihu.com/question/60… zh.wikipedia.org/wiki/%E7%86… leetcode.cn/problems/po…