先从编程之美中一道叫做“寻找故障机器”的题目说起,题目大意是 一个数组,其中只有一个数字出现了一次,其他都出现了两次,问怎样快速找到那个出现了一次的数字。这也就是leetcode上Single Number I这道题。当然了,我们有个对于空间和时间的限制,限制空间为O(1),时间为线性的。若做过这种题,或者经过自己一步步的思考,或许可以想到这个巧妙的方法:将所有数组中的数全部抑或,最后剩下的那个,就一定是只出现一次的数字。说实话一直感觉这种做法很不可思议,只用了一个int的内存就完成了正常O(N)空间才可以完成的,但是,当然了,用O(N)的额外空间遍历时可以记录并“还原”中间出现过的数字,而把他们一股脑抑或,中间过程中,他都是无意义的,只有特定地,只出现一个单独的,其他全是成对,最后的结果才有意义。
然后今天有遇到了这道题的威力加强版,题目如下:
Given an array of integers, every element appears three times except for one. Find that single one.
好了,现在每个数都出现三次,最后一个数可能出现一到两次。说实话一开始太笨,并没有联想到用类似的解法,看到了别人的讨论,才想到可以用类似的方法去做。
考虑每个数都出现两次,我们用到了抑或操作,对于一个数的每位而言,抑或操作其实等价于这个真值表。
result nextnum result
0 0 0
0 1 1
1 0 1
1 1 0
因为每个数都出现两次时有两种状态,0和1,来了0不变,来了1,0变1,1回到0。
拓展来,每个数都出现三次时,每个数就有三种状态,0,1,2 我们自然想到将一个result用两个数分开表示
result nextnum result
a b a b
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 1 1
1 1 1 0 0
这个想法和上面那个类似。
弄出来了真值表我们要用表达式给它表示出来,输入是a b c输出是a b
还记得大二时候学电路逻辑时候,还学到了这种表达方法和化简方法,还记得卡诺图么。。。还记得什么摩根定理么。。。想知道怎么化简自己去查吧~在这里不说了
我们用更简单直接的方法,例如求输出a的表达式,把红色为1的找出来,然后就可以写出 a=(~a&b&nextnum)+(a&~b&~c)
最后上源码:
public class Solution {
public int singleNumber(int[] nums) {
int a=0;
int b=0;
for(int c:nums){
int tempa=(~a&b&c)|(a&~c);//我们这里两个表达式用了卡诺图化简
b=(~a&~b&c)|(b&~c);
a=tempa;
}
return a|b;
}
}
最后为什么是a|b呢,可以自己思考下,提示(单独那个数可能出现一次,也可能出现两次)。