异或(^)作为一个位运算符,它的运算速度是很快的,它的运算规则是将两个数据的二进制数去一一比较,结果是当对应两个二进制位的数相同为0,不同为1,即:
同时异或运算还满足交换律和结合律,这些就使得两个相同的数因为二进制每一位都对应一模一样,所以当两个相同的数在做异或运算时结果为0,即 a ^ a = 0;
因为异或运算满足交换律,所以 a ^ b ^ a = a ^ a ^ b = 0 ^ b,因为b有1的位,而0全都是0位,所以 0 ^ b = b,即0异或任何数都等于这个数本身。
当有一堆数在异或时:
当我们要交换两个数时我们经常会写下面这个swap()方法:
public static void swap1(int[] nums, int i, int j){
int tem = nums[i];
nums[i] = nums[j];
nums[j] = tem;
}
但是在学了异或之后,我们还有一种更快的思路:
public static void swap2(int[]nums, int i, int j){
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
第一次看到这段代码的同学可能会有点阿懵,下面请看图解:
大家根据图解配合异或的规则去想一想就理解了当然这种交换方法在实际中是不建议使用的,只是在这里提供一个思路。
下面来看两道例题:
有一个数列
- 其中只有一个数出现了奇数次,其它数都出现了偶数次,求这个出现奇数次的数。
- 其中有两个数出现了奇数次,其它数都出现了偶数次,求这两个出现奇数次的数。
第一题通过上面的结论可以的出只要拿一个0从头异或到尾就可以得到这个奇数次的数,因为偶数次的数最后都会“抵消”了,代码:
public static int findNumber1(int[] nums){
int num = 0;
for(int t : nums){
num ^= t;
}
return num;
}
第二题相比第一题就复杂很多了
public static void findNumber2(int[] nums){
int o1 = 0;
int o2 = 0;
for(int num : nums){
o1 ^= num;
}
//假设两个奇数为a,b
//因为o1 = a ^ b
//所以o1不为0,那么必定至少有一位是1,
// 而这个1代表a,b在这一位不相等,设a的这个为为1,那么b的这个为肯定为0;
// 经过这个操作,找到o1的第一个为1的位
int rightOne = o1 & (~o1 + 1);
for(int num : nums){
//将nums分成两个阵营,一个阵营那一位为1,一个阵营那一位为0,
// a,b不可能同时出现在一个阵营里
if ((num & rightOne) == 0){
o2 ^= num;
}
}
System.out.println(o2 + " " + (o1 ^ o2));
}
通过上面的介绍,相信大家对异或有了更深的认识,在以后应用时会更方便。