给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
中等难度。我们可以对每个二进制位单独计数,然后统计每个二进制位的1的计数值结果 %3的结果,如果结果是0,则表示只出现一次的元素的当前位为0;如果结果是1,则表示只出现一次的元素的当前位为1。
/**
* 每个二进制位单独计数,然后统计每个二进制位的1的数量 mod 3的结果
*/
public int singleNumber( int[] nums ){
//result表述最终返回的出现一次的数
int result = 0;
//对每个数的每一位进行单独统计
for( int i = 0; i < 32; i++ ){
//每个数的对应位的和
int sum = 0;
//对每个数进行对应位的值进行求和
for( int num : nums ){
//当前数右移i位,并且和1进行&运算,获取当前数的第i位的二进制的值,并且相加
sum += ( ( num >> i ) & 1 );
}
//用和对3取模,获取对应位的运算结果,如果是0表示出现一次的数对应位是0,如果是1表示对应位是1
//然后将运算结果左移i位,再与最终值进行与运算,这样就能让最终值对应位的值变成当前计算的结果值
result |= ( ( sum % 3 ) << i );
}
//返回最终结果
return result;
}
上面这种解法的好处是,此后对于出现了k个一样的数的题目,那么我们使用%k即可求解。
另一种更难理解的方法,使用纯粹的位运算,我们使用两个数,这两个数的对应位来记录最终返回的数的对应位的值的情况。
public int singleNumber( int[] nums ){
int one = 0, two = 0;
for( int num : nums ){
one = one ^ num & ~two;
two = two ^ num & ~one;
}
return one;
}
刚开始时,在第i位,a和b的对应位二进制值都是0。
在第i位如果第1次遇到1,则a对应位二进制值为1,a对应位二进制为0。
在第i位如果第2次遇到1,则a对应位二进制值为0,a对应位二进制为1。
在第i位如果第3次遇到1,则a对应位二进制值为0,a对应位二进制为0,回到原点。
后续的计算以此类推,我们发现,该位最终运算结束后的值就是只出现一次的元素对应位的值。