算法学习之二进制的妙用

4,089 阅读6分钟

有一个笑话,世界上有10种人,一种是看得懂二进制的,一种是看不懂的。 如果你看懂了这个笑话,这篇文章就是适合你读的了

Single Number

leetcode 上有一道这样的题,Single Number,题目是要你要找到数组中唯一只存在一个的数字,其他数字都出现两次。这道题目非常的简单,我们可以用 hash 表来记录所有数字的次数,然后找到次数为1的那个数字。如果用二进制来解决这道题效率会快很多。

二进制的解法

二进制中有一个操作符叫做位异或,他的作用是两个位数字相同则为0,不同则为1,即 1^1=0,0^0=0,1^0=1,0^1=1;

通过这个运算符的特点我们可以知道,任何一个数对自己位异或操作,得到的结果都是 00000000,00000000对任意数字位异或得到的都是那个数字。并且 A ^ B ^ C = C ^ B ^A,这个操作符是满足交换律的。下面看一下 js 的简单解法:


/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
    return nums.reduce((res, cur) => res ^ cur);
};

毒药问题

有 8 个一模一样的瓶子,其中有 7 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 3 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?

解决思路

我们用二进制给每瓶水进行编号,编号分别为,000,001,010,011,100,101,110,111,分别对应1-8的瓶子,然后让第一只老鼠喝第一位为1的,第二只老鼠喝第二位为1的,第三只老鼠喝第三位为1的,假设第四瓶水有毒,即011有毒

  • 第一只老鼠喝了 100,101,110,111,结果:没死,记作0
  • 第二只老鼠喝了 010,011,110,111,结果:死了,记作1
  • 第三只老鼠喝了 001,011,101,111,结果:死了,记作1

根据死亡结果,刚好是第四瓶水011,这只是一个巧合吗,恐怕不是的,我们可以用数学的思维来证明一下这个问题

证明

每只老鼠喝了毒药只会出现两种情况死或者不死,一只老鼠可以验证两瓶药有没毒,即2^1,两只老鼠可以验证2^2瓶药,三只老鼠可以验证2^3瓶药,那么要怎么去验药呢

  • 我们让每只老鼠喝某一位为1的的所有药,如果那只老鼠死了则说明毒药的某一位编号为1,比方说第一只老鼠喝了所有第一位为1的毒药死了,则说明毒药的第一位编号为1,如果没死,则说明毒药的那一位编号为0
  • 这里如果三只老鼠都没死,则说明毒药的三位编号都为0,刚好是三只老鼠都每没喝的第一瓶。

拓展问题

有 1000 个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?

根据上面的问题,我们能够知道 2 ^ 10 = 1024 > 1000,也是通过对每个瓶子进行二进制编号即可检验出哪个瓶子有毒。

问题升级

现在,有意思的问题来了:如果你有两个星期的时间,为了从 1000 个瓶子中找出毒药,你最少需要几只老鼠?注意,在第一轮实验中死掉的老鼠,就无法继续参与第二次实验了。

拓展问题思路

我们要达到的目的是用尽可能少的老鼠,在两周之内找到结果,所以我们必须要进行两轮的实验,那么每只老鼠可能就会出现三种情况,第一轮死掉,第二轮死掉,第二轮活着,上面一题老鼠会出现两种情况用的是二进制,那么这一题很明显我们需要用到三进制。3 ^ 6 = 729, 3 ^ 7 = 2187, 很明我们至少是需要7只老鼠。

如何喂药

  • 还是和前面一样,第一轮的时候,我们让每只老鼠喝某一位为编号为2的药,如果某只老鼠死了,则说明毒药的那一位编号为2,如果老鼠全死了,我们连第二轮都不用了,直接可以确定毒药的编号为2222222。
  • 第二轮如果还剩多少只老鼠,则说明毒药有多少位的编号为0或者1,我们剩k只老鼠,k位二进制数需要确认,因为那几位数已经排除了是2的可能性。则由回到了上一题,我们继续用上一轮的喂法即可。

称重问题

27个小球。其中一个比其他小球都要重一点。给你一个天平,最多称3次,找出这个特殊的小球。

思路

这题也是需要用到三进制的思路来解决的,我们每次称可能出现三种状态左边重,右边重,一样重,3 ^ 3 刚好27,所以我们是可以在3次内找到这个小球。

如何称

先给每个球编号000,001,002,010,...,222

  • 第一次称第一位为2的和第一位为1的所有小球,他们同样都是9个,哪边重则说明,较重的小球的第1为几,如果一样重则说名第一位为0
  • 第二次称第二位为2的和第二位为1的所有小球,他们也同样都是9个,哪边重则说明,较重的小球的第2位为几,如果一样重则说名第二位为0
  • 第三次称第三位为2的和第三位为1的所有小球,他们也同样都是9个,哪边重则说明,较重的小球的第3位为几,如果一样重则说名第三位为0

经过三次称重我们就可以找到较重的哪一个小球了。

总结

面对这种问题,其实解决思路都大通小异,都是需要找到问题关键状态,通过关键状态的数量我们可以知道需要用到多少进制来解决问题。然后根据题目制定方案,来确定每一位的状态码最终得到结果。