题目描述:
在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。你可以假设满足条件的数字一定存在。
思考题:
- 如果要求只使用 O(n)的时间和额外 O(1)的空间,该怎么做呢?
样例
输入:[1,1,1,2,2,2,3,4,4,4]
输出:3
分析:
方法一:
统计各位1出现的次数。我们知道,如果没有那个出现了一次的数,其他数字都出现了三次。对于任意一个数,在计算机里存储的是32位二进制数,同样的数出现三次,则将这些数从0到31对应的位数统计一下,要么是0,要么必然是3的倍数。如果把出现一次的数x也统计进去呢,那么0-31某位上的1能够被3整除说明x对应位是0,否则就是1,这样就知道了所求数各位上是0还是1了。
class Solution {
public:
int findNumberAppearingOnce(vector<int>& nums) {
int b[32];
memset(b,0,sizeof(b));
for(int i = 0;i < 32;i++){
for(auto x : nums){
if(x >> i & 1) b[i]++;
}
}
int ans = 0,k = 1;
for(int i = 0;i < 32;i++){
if(b[i] % 3) ans += k;
k <<= 1;
}
return ans;
}
};
方法二:
基于状态机求解。
我们需要一种运算,与0做该运算不改变状态,与1做该运算三次则回到原状态。类似于一个长度为3的环,做三次运算会回到原点。设有两个数ones和twos,初始转态为零,记为(0,0),执行下面代码的操作,x=1,时,状态变为(1,0),再执行一次得到状态(0,1),在执行一次得到(0,0)。也就是经过三次操作回到原始状态。如果x为0呢,(0,0)变为(0,0),(1,0)变为(1,0),(0,1)变为(0,1),发现并不会改变状态。与方法一同理,各位执行三的倍数操作则会回到原点,再执行一次操作,x = 0则不改变状态,x = 1,则从(0,0)变为(1,0),这里的ones也就是我们所求的出现一次的数了。更具体一点,就是(0,0)与所有出现次数为3的数做该运算最后状态还是(0,0),再与出现一次的数(记为t)做该运算,t对应的位是0则不改变状态,是1则将ones对应的位置为1。
class Solution {
public:
int findNumberAppearingOnce(vector<int>& nums) {
int ones = 0,twos = 0;
for(auto x : nums){
ones = (ones ^ x) & ~twos;
twos = (twos ^ x) & ~ones;
}
return ones;
}
};