趣味算法:国王和100个囚犯(据说是腾讯的面试题)

在其它地方看到一道题目,估计有不少园友也已经看过了,也有同学解了,但本人比较愚,当时看到这个题目,我都快蒙了,还好有好心人给了些思路,于是,慢慢摸索着,用我们伟大的面向对象的思想来解这道题(虽然这道题跟面向对象没有半点关系)。
题目如下:
    国王招来100个囚犯,对他们说:你们犯的是死罪,但我给你们一次求生的机会。15分钟以后,你们将被关进一个有100间隔离牢房的监狱里,每人一间牢房,都与外界隔绝,什么也听不见看不到,连时间都没法计算,无法获得外界的任何信息。 
    这所监狱有一个院子,每天只少随机(注意是完全随机)打开一间牢房的门,让一个囚犯到院子里来放风。院子里有一盏灯,放风的囚犯可以控制它的开关,将它打开或是关闭。除囚犯之外,其他人都不会去碰开关。这盏灯会永远有充足的能源供应,灯泡和电路不会出故障。 
    除了开关这盏灯,放风的囚犯放风时留下的任何其它痕迹都会在夜晚被清除干净(包括在灯上作的任何记号)。牢房是完全封闭的,院子里的灯光在牢房里看不到。只有放风到院子里的人才能看到。 

    好了现在我向你们提出一个要求,只要你们做到了,就可以全部获得释放: 
    给你们15分钟商量你们的方案。15分钟以后,你们将被关进我刚才说的那个监狱,永远无法再交流,被关若干天后,你们中间如果任何一个人能够向我证明你们每个人都至少放风了一次,我就把你们放了,不然永远别想再出来。 
    好吧!这样吧,如果你们有谁现在可以告诉我这个方法,也就是能够证明你们每人至少放风一次的方法,我马上放掉你们! 

    其中一个囚犯想了几分钟,回答了这个问题,国王听后,如自己所说的把他们全部给放了。请问那个囚犯是用什么方法证明的?

 

大致的解题思路(建议思考后再看): 

代码

囚犯们指定一人为计数人(A),A以外的囚犯每次出来放风时,如果看到灯是关闭的,则将灯打开,但如果已经开过一次灯,则不理会,当A出来放风时,如果灯是开着的,则将灯关掉,关一次则表示有一个人出来了一次,计数至100,完成。

 

分析一下,这里面参与求解的东西无非就是监狱、灯、囚犯(计数员)。

监狱:没有什么动作需要做的。

灯:有一个状态标识, 提供开灯和关灯的方法。

囚犯:有一个是否开过灯的标识,并带有一个开灯的方法 

计数员:继承囚犯,且拥有关灯的方法。 

 

实例代码:

监狱:

Prison

 


再建立一个灯的类:

Light

 


接下来是囚犯:

Prisoner

 

 

还要有人负责计数:

代码

 

 

再定义一个Helper来显示文字记录:

代码

 

 

测试代码:

代码
 1           static   void  Main( string [] args) {
 2              var prison  =   new  Prison( 100 );
 3              prison.Run();
 4               int  len  =  prison.Prisoners.Length;
 5               for  ( int  x  =   0 ; x  <  len; x ++ ) {
 6                   if  ( ! prison.Prisoners[x].LightOn) {
 7                      Console.WriteLine( " Wrong!!!囚犯({0})还没有出来放风过 " , x);
 8                      Console.ReadLine();
 9                  }
10              }
11          }

 

  

 至此,所有的类已经定义完毕。执行Main方法即可得到答案。 计算结果如下(片段):

  1 2042-12-28    第 11828 天

 2  囚犯( 49 )出来放风
 3  灯是开着的
 4  2042 - 12 - 29     第  11829  天
 5  囚犯( 75 )出来放风
 6  灯是开着的
 7  2042 - 12 - 30     第  11830  天
 8  囚犯( 70 )出来放风
 9  灯是开着的
10  2042 - 12 - 31     第  11831  天
11  囚犯( 66 )出来放风
12  灯是开着的
13  2043 - 01 - 01     第  11832  天
14  囚犯( 69 )出来放风
15  灯是开着的
16  2043 - 01 - 02     第  11833  天
17  囚犯( 51 )出来放风
18  灯是开着的
19  2043 - 01 - 03     第  11834  天
20  囚犯( 58 )出来放风
21  灯是开着的
22  2043 - 01 - 04     第  11835  天
23  囚犯( 92 )出来放风
24  灯是开着的
25  2043 - 01 - 05     第  11836  天
26  囚犯( 39 )出来放风
27  灯是开着的
28  囚犯( 39 )关灯
29  灯被关闭了
30  囚犯( 39 )已经统计到所有囚犯全部出来放风过了,任务完成了

 

 

乐观的结果是27年左右,计数人就可以统计到所有囚犯全部出来放风一次,但前提条件是,这27年中不能有人挂掉,要不然,计数人就永远Count不到100个,也就永远出不来了。上面的代码,可以适应国王与N个囚犯,但是当囚犯达到150+的时候,基本上就都不用出来了,因为150人时已经差不多需要70年了。。。

 

附项目文件下载: KingAndPrisoner

 

其实,这里面有一个问题,在我的代码中,创建监狱实例时,我们就将灯的初始状态置为关了,就表示我们的代码只有在假定灯初始状态为关的情况下才有效,如果灯的初始状态不定,那该怎么解呢?我想了很久,没有找到答案,不知道有没有人能告诉我答案,谢谢。

 

最新更新解法: 

由Curry同学提供的,每人开灯两次则可以解决灯初始状态不确定的问题.

解题思路:

N=囚犯数量

计数总数 = N * 2 - 2

计数人只关灯不开灯,如果N = 100,则需要关灯的次数为 100 * 2 - 2 = 198.

假设灯初始状态为关,则计数到198时,其它的99个囚犯都已经开过两次灯.

假设灯初始状态为开,则计数到198时, 198 = 98(囚犯) * 2(次) + 1(囚犯) * 1(次) + 1(灯初始状态开) 

 

修改后的代码:KingAndPrisoner20100810.7z 

转载于:https://www.cnblogs.com/liyazhou/archive/2010/08/10/1796914.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值