【算法】用计算机思维求解“12个硬币其中一个为假,三次称量找到假硬币”问题(附代码)

背景

题目是这样子的:

有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
  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值