首先让我们来看看什么是异或运算
异或运算:相同为0不同为1
同或运算:相同为1不同为0
同时我们还需要知道异或运算的性质
1. 0 ^ N == N
2. N ^ N == 0
这两个性质非常重要哦!待会会频繁用到
接下来我们从题目开始:
题目1:
如何不用额外变量交换两个数?
大家都知道在Python中是不需要的,那么如果在Java中我们就需要使用额外的一个变量先去记录一个值,然后再去交换。
像是这样: int a = 10;, int b = 5;,我们就需要先用一个临时变量记录其中一个值, 例如:
int temp = a; 然后再去交换 a = b, b = temp 这样我们的交换就完成了。
但是这需要用到额外变量,那么我们如何不适用额外变量就能替换两个值呢?我们就需要用到异或了 。
根据异或的性质,N 异或 N == 0,所以我们可以 用 a 异或 a 然后再异或 b,a是不是就变成了b呢?
让我们来看看代码实现,其实就是这么简单的几行代码,我们用a来替代了额外变量,第二行就相当于a ^ b ^ b我们就得到了a,然后让b接收了这个 变量我们就进行了交换。然后再用新的b也就是a,异或 a ^ b,也就是 a ^ b ^ a,得到了b。这样我们就完成了交换
a = a^b;
b = a^b;
a = a^b;
题目2:
一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数字?
我们同样也可以用到之前的异或的性质,两个相同的数字如果异或一定为0,所以我们只要将所有的数字异或,我们就能得到出现了奇数次的数字。
让我们来看代码:
申请一个变量接收异或的结果,然后循环数组中所有数字,和额外变量异或,最后返回结果就行了
public class QuestionTwo {
public static int oddTimeNum(int[] arr){
int eor = 0;
for(int num: arr){
eor ^= num;
}
return eor;
}
}
题目3:
如何把一个int类型的数,提取出二进制形式最右侧的1来?
提取最右侧的数字我们只要 !!!把这个数字和它的负数做相位与就好了
我们先来说说在二进制里面一个负数是怎么表示的,首先将这个数上的所有位取反然后+1
例如数字3:二进制表示是011,负数就是取反100,然后+1,就是101,
然后我们再将 3 和 -3做相位与 011 & 101,答案就是001这样我们就提取出来最右侧的1了。
前面都是我缩短了之后实际上在计算机里面的状态是
(这个表示3)
(这个是反码)
(+1变成 - 3)
(和原来的3与完之后)
代码只有两行
int a = 3;
int rightOne = a & (-a);
题目4:
题目2的升级版,如果有两个数出现了奇数次,其他数字出现了偶数次,怎么找到这两个奇数,并且打印出来?
听起来很难?其实思路也是利用异或的性质。
我们先将所有数字异或,这样我们就能得到a 异或 b(因为出一个数字偶数次异或都为0)。
然后我们在提取出a异或b的二进制上的任何一个1,为什么要这样做?因为只有a或者b上有1那一位才有1,所以我们拿着这个1和原数组中这个位置上有1的进行异或,我们是不是就能把其中一个数字提取出来?
然后我们再将这个数字和a异或b去异或,这样我们就能拿出另个一数字。
为了方便我们取a异或b最右边的1,上面那题已经说过了,我们将a异或b用变量eor来表示,就是eor逻辑与上-c
接下来我们来看代码:
public static void OddTimeNum(int[] arr){
// 用来存储数组中所有数字都被异或了的结果
int eor = 0;
for(int num : arr){
eor ^= num;
}
// 提取最右侧的1
int rightOne = eor & (-eor);
// 存储其中一个数字
int oneOfTwo = 0;
for(int num : arr){
// 如果这个数字上那个位置有1,就不是我们想找的
if((num & rightOne) != 0){
oneOfTwo ^= num;
}
}
System.out.println(oneOfTwo);
System.out.println(eor ^ oneOfTwo);
}
Reference: https://italk.mashibing.com/course/detail/cour_00004003