背景
题目是这样子的:
有12枚硬币,其中有一枚假币,而且真币与假币谁轻谁重不知,如何通过三次称量判断出哪枚是假币?
这是一道很经典的逻辑推理题,乍一看感觉很想脑筋急转弯,但是其实完全可以用计算机思维去系统的解这道题。
如果能够掌握系统的思维,这样就可以举一反三,解决更多的问题。——毕竟总不能什么都靠人来推理吧?万一数据量变大了怎么办呢?例如39个硬币称4次要怎么称?
这道题的关键:状态。——天平的状态、硬币的状态。
思路
首先,天平的状态只有3种可能:左边重、右边重、平衡。
如果用计算机语言表示:左边重表示为0、右边重表示为1、平衡表示2。
那么称量1次的结果就只会是:0/1/2。
那么称量3次的结果,可以表示为:3个0/1/2。——本质就是个3进制的三位数。
例如称量3次的结果为010的话,就表示:第一次左边重、第二次右边重、第三次左边重。
既然天平称量3次的状态是个3进制的三位数,那么就一种有27种可能。
对于硬币的状态,有12个硬币。其中一枚是假币,且不知道假币是偏重还是偏轻。那么就一共有24种可能。——即每一个硬币都有可能是假的,且有可能是偏轻、也有可能是偏重,所以是12*2=24。
显然,天平称量3次的状态的数量(27种),必然大于硬币的状态(24种),所以称量3次一定是可以找那枚假币的。
我们可以通过设计,把硬币的每个状态都和天平的状态对应上,例如:天平称量3次的结果是001,设计这种状态为硬币1为偏重。那么反过来,硬币1偏轻对应的天平状态就是110。
其实用的就是计算机状态编码的思想。
编码
现在问题在于我们怎么编码呢?下面就来找编码的规则。
规则1:
对于同一个硬币,偏重和偏轻的编码,是其对应位置必须1和0对应。例如硬币1偏重的编码是201,那么硬币1偏轻的编码是210。201表示第一次称重硬币1没放上天平,所以如果硬币1偏重的话,由于不在天平上所以天平结果是平衡。第二次称重硬币1在左边,因为偏重所以左边重。第三次硬币1放右边,因为偏重所以右边重。
如果硬币1是偏轻的话,因为第一次没有上称,所以2不需要变。只需要把第二次改为右边偏重、第三次左边偏重即可,所以变为了210。
规则2:
被用过的天平编码状态不能被重复使用。例如硬币1偏重的编码是001,那么硬币1偏轻的编码是110。那么001和110这两个状态就不能给其他的硬币用了,因为如果给硬币2偏重是110的话,如果最后天平出现了110的情况,我们无法判断是硬币1偏轻还是硬币2偏重。
规则3:
其实在计算机状态表示的时候,有一个隐藏的前提:天平两边的球数量一定相等。(如果数量不想等的话,天平就没有意义了)
这个限制条件转化为编码层面上体现的话,即为:24种硬币状态的编码,对于同一个位置的0和1的数量要相等。
例如假如三位数的第一位表示第一次称重,如果有8种硬币状态的第一位是0,4种硬币状态的第一位是1,那么就意味着在第一次称重是,在天平的左边要放8个、右边放4个。这样子天平两边数量就不相等了。所以必须保证0和1的数量要一样。
规则4:
000和111是不可用的编码。因为如果一个硬币X偏重的编码是000,那么硬币X偏轻的编码是111。这样就会导致111这个编码被占用,没办法给除了硬币X以外的硬币,所以无法满足规则3。
根据上面这几点,可以自己手工运算编码,或者是写代码来算出来。代码如下:
def to_ternary(x, weight_num):
# 十进制转三紧致
s = ''
temp1 = 1
while temp1 != 0:
temp1 = x // 3
temp2 = x % 3
x = temp1
s = s + str(temp2)
return '0' * (weight_num - len(s)) + s[::-1]
def verse_code(code_ter):
code_ter_verse = code_ter.replace('0', 'X')
code_ter_verse = code_ter_verse.replace('1', '0')
code_ter_verse = code_ter_verse.replace('X', '1')
return code_ter_verse
def coin_other(code_ter):
coin_other_ter = ''
for c in code_ter:
c = int(c) + 1
if c == 3:
c = 0
coin_other_ter += str(c)
return coin_other_ter
def encode_coin