找出那个出现一次的数

本人算法没什么天赋,但时常想拿个小题想想,倒没什么功利(例如找工作)目的,充实一下时间。


曾经出现频次很高的一个问题,说一个整数序列中有除了一个数字出现一次,其余都出现了两次。现在让你扫一遍数组O(n)找出那个数。方法是将所有的数作异或操作,结果就是你要找的。
leetcode 137 对这个问题升级了一下,现在问你,其余的数都出现三次呢,该如何找出那个出现一次的数。我感觉对我来说还是挺难的,解决方法很巧,在下佩服。理一下思路。

先回忆一下为什么异或可以解决第一个问题,其实本质上是考虑了每个整数序列中的每个二进制位1出现的次数,要么出现偶数次,要么出现奇数次。并且如果那个要寻找的数在该位置上有数字的话,该位1一定出现奇数次,即最终异或结果该位值为1.

对于出现三次就这个方法不灵了啊,别着急,再想想,可不可以实现这样一个规律,该二进制位上的数如果出现三的倍数,该位置零。那么是不是每一位上都得有个计数器啊,不不不,这个空间复杂度有点高了。容我们再想想。

哦,对了! 三个的话只需要一个2bits的比特位就够了啊。那么其实两个整数就够了。即我们只需要有一个2bits 的循环计数器,按照00->01->10->00 的方式计数,就知道该位1的格式是不是3的倍数了!! 声明两个整数分别代表计数器的两个bit位: int ones = 0 , twos =0, 并设数组序列是a 。要分开看这两个比特位,找下规律。

0->0->1->0   twos

0->1->0->0   ones

观察规律发现

twos前一位是1的时候 将ones的当前位清0.

ones当前位是1的时候 将twos的当前位清0.

其余情况按照位加法递推。

由此可以得到以下递推式,意义是当扫描到a[i]这个数的时候,计数器的变化公式。

ones =  (ones ^a[i])&~twos

twos =   (twos^a[i])&~ones

完整代码示例:

public int singleNumber(int[] A) {    
   int ones = 0, twos = 0;    
   for(int i = 0; i < a.length; i++){        ones = (ones ^ a[i]) & ~twos;        twos = (twos ^ a[i]) & ~ones;    }    
   return ones; }

给我感觉很神奇的,但是---------还可以再升级一下,除了一个数出现1次,其余的都出现五次,怎么弄。

按照相同的逻辑,我们要构造一个循环计数器按照 000->001->010->011->100->000 的方式循环。我们再来拆开看看。


0->1->0->1->0->0  ones

0->0->1->1->0->0  twos

0->0->0->0->1->0  threes


观察发现

当ones前一位为0的时候,twos值不改变

当threes前一位为0的时候,ones的当前位置0

当且仅当ones和twos当前位都为0的时候,按照正常位加法置位。

其余情况按照正常加法置位。

由此可以写出以下递推式


twos = twos^(a[i]&ones)

ones=  ones^(a[i]&~threes)

threes=threes^(a[i]&~ones&~twos)


完整代码示例:

public int singleNumber(int[] A) {    
   int ones = 0, twos = 0,threes =0;    
   for(int i = 0; i < a.length; i++){       twos = twos^(a[i]&ones);
      ones = ones^(a[i]&~threes);
      threes= threes^(a[i]^~ones^~twos);    }    
   return ones; }

我能吐槽以下这个递推式并不好归纳吗。真的,如果有较完整的方法的话就好了,少费点我这老年人的脑细胞了。

其实如果允许我们存储临时变量的话,本质上递推式就是一个逻辑函数。怎么讲呢。 也就是说,前面的几个比特位和a[i]在该位上的值共同决定了下一位的取值。我们完全可以列出一个真值表。对于三个比特位的例子列出如下,其中ones twos threes 表示前一位,ones'  twos' threes' 表示当前位。


有了真值表如何得到逻辑函数呢,我觉得大家可能都还给老师了,我想到了,是数字逻辑课上学的卡诺图,卡诺图可以对逻辑函数化简的。理论上可以解决此类通用问题。

其实本科学的有些东西还是很有用的。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值