只出现一次的数字II
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以使用常数空间来实现吗?
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:
输入: [0,1,0,1,0,1,99]
输出: 99
思路:
如果用hash和set这道题目明显是很容易的,但是我们需要满足使用常数空间解决问题。于是就出现了一种思路,因为除了只出现一次的数,其他数出现都是3次,那么统计每一位出现的次数,通过位运算,我们找到出现次数为1的那些位,这些位=1时组成的数就是那个只出现一次的数。
所以现在问题就是这个位运算怎么做才能得到出现次数是 1的那些位。
1.定义ones记录出现一次的数,ones和新来的数字x做按位异或,有以下情况:
1)1^0=1,意思是原本出现次数是1的位,新数字这一位是0,所以在ones中这一位还是1。
2)1^1=0,原本出现次数1的位,新数字这一位也是1,所以ones中这一位变为0,因为这一位出现次数已经不是1了。
3)0^1=1,原本出现次数是0/3或2,所以这里其实有问题的,我们要把原本出现次数是2的去掉才是原本出现次数0,现在出现次数1的。
4)0^0=0,原本出现次数为0/3或2,现在新数字该位是0,所以在ones中这一位还是0。
2.通过确定原来是出现两次的找到新的出现0/3次的位,用来在ones中去掉。
之前出现过两次的,这次再出现就是出现了三次,用&,只有原来1,新来的也是1,最后才是1。
int threes = twos & x;
3.每次记得更新twos。
// 之前出现过两次,这次没出现,是出现了两次。
// 之前出现过一次的,这次再出现,也是出现了两次。
twos = (twos & ~x) | (ones & x);
代码:
class Solution {
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for (int x: nums) {
// 之前出现过两次的,这次再出现就是出现了三次
int threes = twos & x;
// 之前出现过两次,这次没出现,是出现了两次。
// 之前出现过一次的,这次再出现,也是出现了两次。
twos = (twos & ~x) | (ones & x);
// 统计记录出现了奇数次的,并从其中清除出现三次的。
// 这样ones里面始终只会记录出现了一次的。
ones=(ones^x)&(~threes);
}
return ones;
}
}