【学无止境】据说Google内部有史以来最难的一道面试题
逛技术平台的时候,刷到一道算法题,一眼看去,就被其开头吸引了:
摘自知乎某 Google 分布式大神的一道题,技术是Google内部出的有史以来最难的一道题
嗯,距离下班还有一段时间,就看看把。
题目
包括你在内的一群计算机科学家被一帮邪恶的『数学毁灭教』的教徒们抓住了,各自分开关押,人数未知。现在,你被允许写一封邮件给狱长,狱长会帮你把这封邮件转发给其它所有人。在此之后,你们之间的交流将变得十分有限:
从明天起,每天上午,每个人必须写下一个0或者一个1,然后由狱长收集。狱长收到所有人的信息后,在心中将你们排成一个圆(顺序可以由狱长任意决定,每天都可能不一样)。
然后在当天下午,狱长会把每个人写下的那一位数字分别交给圆中位于这个人顺时针方向的下一个人。
如果某天,某位计算机科学家可以自信地把被抓的人的总数告诉狱长,并且答案正确,则全体释放;若答案错误则全体处死。
问:
(Easy)提供一个方案,使得至少99%概率下所有人能释放。
(Hard)提供一个确定性方案,使得保证所有人都能释放。
注意:狱长可能随机决定每个人在圆中的排列顺序,也可能根据你写给大家的邮件内容,或是当天大家写的数字等等,来排出某个有针对性的顺序。
注意2:所有人都是长生不老的。😛
其他人的分析
一开始想了一会,思路有限,于是点开看评论,发现有一条疑似答案的:
一个简单的解决方案,写信的人每天写1,其他人一开始写0,后面收到什么写什么。当写信的人连续N天(N表示1的个数)收到1的时候。那么根据概率论,是能得到一个近似值。后面再写0。可以来回验证概率的准确性。时间是无限的。那么概率会无限接近于真相。也就是100%。问题在于这种方案永远无法得到一个100的准确率
仔细想来不可行,原因在于该方案的基础是概率论,也就是随机性,但是题目中典狱长开了上帝视角,可以决定抛骰子的概率,随机性这一条就不符合了。也就是说,典狱长在一开始知道你的策略之后,可以做针对。
反例,假设A为写信者,B为第一天A的下线,C为第二天A的下线。从第一天开始,总是把A的【前一天】的下线作为【下一天】A的上线,则该组中永远只有不超过2个1,且A每天收到的都是1。
D1
Xj/0 -> A/1 -> B/0 -> C/0 -> Xi/0
D2
Xj/0 -> B/1 -> A/1 -> C/0 -> Xi/0
D3
Xj/0 -> C/1 -> A/1 -> B/0 -> Xi/0
D4
Xj/0 -> B/1 -> A/1 -> C/0 -> Xi/0
可以发现,D2-D4可以无限循环下去,形成死局。
我的分析
上面的答案虽然不正确,但是无疑打开了我的思路。顺着想下去,发现似乎有戏。
首先,可以做一个合理的假设:数学家的人数是有上限的,最大值为M。这里给个预估值100人以内,即被关押的人数在1-100以内。(这个预估值是为了便于理解,取一千,一万都可以,有穷的就行。如果是无穷的话本身题目就无解。)
其次,问题的关键在于,如何在有恶意干扰的情况下,在组内传播有效信息,即这种传播应该是像病毒一样具有强扩散性的。
分析这条规则:写信的人每天写1,其他人一开始写0,后面收到什么写什么。
可以把写信人理解为1的源头,标为黑色,其他人一开始都是0,为白色。写信人可以生产黑色,其他人只能传递黑色,只有当写信人接触到一个新的其他人,组内才会多一个黑色。
在随机性成立的条件下,黑色会以一个固定的速度生长并传播,但是由于典狱长的存在,他可以掐断黑色的生长。
那么把规则稍稍修改:
- 规则1:写信的人(起个名字:智能人)每天写1,其他人(起个名字:沉睡人)一开始写0,如果沉睡人收到1后,就被唤醒,变成智能人,也开始写1,否则的话还是收到什么写什么,这条规则在一开始告诉所有人。
- 规则2:写信的人预估总人数的上限值为M,并告诉所有人。
这里先说下什么是智能人,沉睡人:
- 智能人:知晓其他智能人的存在,即知晓当前组内智能人的人数。
- 沉睡人:啥也不知道,只能根据一开始的规则行动。
至于这种特性怎么来的,下面会说到。在此规则下,再加上后续的补充规则3,可以分析得到:不管典狱长怎么捣鬼,最终所有的沉睡人都会被唤醒,变成智能人,即知道组内人数。
进一步分析
举例,第一天后,即第二天早上,A,B都是智能人,继续写1,然后典狱长坏坏地在排序上做了手脚。第三天早上,有两种情况:
1.多了1个智能人。(AB相邻,A->B->C或者B->A->C,唤醒了C)
2.多了2个智能人。(AB不相邻,A->C->B->D或者B->C->A->D,唤醒了C,D)
也就是说,唤醒的速度是[1,n],n为当前轮次组内智能人的数量。即每轮至少有一个人会被唤醒,最多有n个人被唤醒。
那么,现在的问题是,如何确定有几个新人被唤醒了呢?下面说:
第一天
- 初始状态:组内有(确定的)1个智能人A。
- 操作:第一轮唤醒。即A写1,其他人写0。
第二天
- 前一天结果分析:A可以根据第二天收到的数,1或者0,知道组内是只有1人或者至少2人。
- 第一种情况:收到1,这个1必然只能来自A自己,即组内只有1人,所有智能人都已经被唤醒,游戏结束。
- 第二种情况:收到0,组内至少有2人或更多,且此时场上有两个智能人A,B。
- 上述是A的视角,即A可以凭借信息做出的分析。那B呢?同样地,只要他第二天收到了1,则必然是情况二,此时A和B信息对等,即相互知道彼此的存在。下面就对情况二再作分析。
- 初始状态:组内有(确定的)2个智能人A,B。
- 操作:第二轮唤醒。即A,B写1,其他人收到什么写什么。
第三天
- 前一天结果分析:此时,A,B无法根据收到的数,1或者0,确切知道新唤醒的智能人个数。那么,他们的任务就是:去确定经过第二天(第二轮唤醒)后,组内的确切智能人人数。
- 初始状态:组内有3-4个智能人。
- 操作:第二轮唤醒后的第一次交流。其他人都不说话,首先A来说,我昨天收到的是0还是1。(至于这个怎么说,后面会详解)经过交流之后,B知道了A写的数。
第四天
- 操作:第二轮唤醒后的第二次交流。其他人都不说话,然后B来说,我昨天收到的是0还是1。经过交流之后,A知道了B写的数。此时,A,B的信息再次对等,交流环节结束,可以开始分析。(第二轮唤醒前,组内有两个智能人,所以需要两次交流。)
- 结果分析:如果A,B一共收到了:
- 0个1:上一轮唤醒了2个智能人。
- 1个1:上一轮唤醒了1个智能人。
- 2个1:上一轮唤醒了0个智能人,所有人都被唤醒,上一轮智能人数就是组内人数!
这个分析挺显而易见的,就不展开了。这里说一下怎么实现交流:“A来说,我昨天收到的是0还是1”。道理和上面说的规则1类似,规定当前轮次为A的交流轮次,只允许A发言1或0,其他人都发0,如果其他人收到了1,就一直是1。
当轮次足够多时,比如预估总人数100人以内,那么运行100次以上后,组内所有人都会传播为A的发言。这时候B就知道了A的信息。这里有一个比较tricky的地方,就是一开始,写信人A把总人数的预估值M设置为多少,理论上说,当M无穷大时,即A至少连续M-1天收到1时,此时可以认为信息已经充分传播。
那么,此时的C或者D,和A,B信息对等吗?答案是对等的。因为两次交流的结果,是全组人都知道的,C或者D必然知道了A,B写的数,也能推理出第二轮新唤醒的人数,也能知道此时组内智能人的总人数!
第五天
- 然后,就又返回到唤醒的轮次,组内的3个或者4个智能人,再做一遍之前的唤醒操作。然后是唤醒后的交流操作,最后最分析。这样循环往复,直到上一轮唤醒了0个智能人,GameOver!答案就出来了。
那么,规则3:即为上述的唤醒+交流+分析的所有思路,通知所有人。
总结
推理完毕,发现题目的难点在于由于有典狱长的存在,让任何“阴谋”无法实现,那么只能用“阳谋”。我就把我的策略告诉你,并且这个策略无法被针对,类似某些棋类游戏的先后手必胜策略。
来源
本题出自:掘金网@razertory用户在2018年于“每天一道算法题”专栏发的一个帖子。