例1:在一个数组里面存在1个只出现奇数次的数,其他数均出现了偶数次,请找出这1个只出现奇数次的数。
分析:
题目中两种数据的区别在于出现的次数为奇数或是偶数,我们应该利用这一点来解题。
我们知道 ^ (异或)运算的特点有:
1. 0 ^ a = a, a ^ a = 0
2. ( a ^ b ) ^ c = a ^ ( b ^ c ), a ^ b = b ^ a
3. 同一组数据只要进行 ^ , 结果相同
现如果对数组内的所有数进行异或操作,那出现偶数次的数均会因 a ^ a = 0, 而变为 0,最后只会剩下 0 ^ 我们寻找的数。
代码实现:
int eor = 0;
for(int i = 0; i < arr_len; i++)
{
eor = eor ^ a[i];
}
eor即为我们搜寻的那个数
例2:在一个数组里面存在2个只出现奇数次的数(两个数不同),其他数均出现了偶数次,请找出这2个只出现奇数次的数。
分析:
设所求两个数分别为a, b, 按例1操作,设eor,并对数组全部元素进行异或操作,得到 eor = a ^ b , 如何分离出 a , b ,就是我们面临的问题 。
因为 a, b 为不同的数,所以eor = a ^ b 不为 0,即eor的二进制表示中必存在1,并且我们由 ^ (异或)的原理可知a,b 在存在的1位置上一个为0,一个为1,为了便于区分我们只取eor在二进制下的最后一个1作为参照,并令 a的该位置为1,b的该位置为0。并且因为已知 eor = a ^ b 的值,我们只需求出a 或 b 的值便可计算出另一个的值,我们不妨先求a的值。
对于a,其x位置上为1,我们将数列中所有x位置上为1的数进行 ^ (异或)操作,因为符合要求的数除了a以外的所有数均为偶数个,所以异或操作后的结果为 a。我们便求得a,继而可以求得b。
int eor = 0;
for(int i = 0; i< arr_len; i++){
eor ^= arr[i];
}
//eor = a ^ b
//eor != 0
//eor 必然有一个位置上是1
int rightOne = eor & (~eor + 1); //提取出最右边的1
int onlyOne = 0;//eor',相当于新建一个eor用以储存第二次异或的结果a的值
for(int i = 0; i < arr_len; i++){
if((arr[i] & rightOne) == 1){
onlyOne ^= arr[i];
}
}//对x位置为1的所有数进行异或操作,计算出a
//a = onlyOne
//b = eor ^ onlyOne
此例中可以总结出:
如何获得一个数在二进制下只保留最右边的一的形式
//如何获得只保留一个数在二进制下最右边的1,其余位均为0的形式
//a为我们所用的数
int rightOne = a & (~a + 1); // 只保留最右边的1,其余位置均为0
图解