今天闲话不用多说,先来看一个小例子。
如何交换两个变量的值(整数)?
问题很简单,交换两个数,常规的做法是引入一个中间变量,代码如下:
public static void main(String[] args) {
int x = 10, y = 20; //定义两个变量
System.out.println("交换前 x=" + x + ",y=" + y);
int temp = x;
x = y;
y = temp;
System.out.println("交换后 x=" + x + ",y=" + y);
}
相信上面的代码大家应该都没问题,现在我们提高难度,在不引入第三个变量的情况下,实现两个数字的交换。
有同学可能会想到通过用「两个数求和,然后相减」的方式实现。代码如下:
public static void main(String[] args) {
int x = 10, y = 20; //定义两个变量
System.out.println("交换前 x=" + x + ",y=" + y);
x = x + y; //x = 30
y = x - y; //y = 10
x = x - y; //x = 20
System.out.println("交换后 x=" + x + ",y=" + y);
}
上面这种方式实现两个数字的交换,而且没有引入第三个变量。在这里先给你点个赞。
但是这种方式存在问题:如果 x 和 y 的数值过大的话,超出 int 的值会损失精度。
那如何在不借助第三个变量实现两个数的交换,同时又保证不损失精度呢?
好了,不再卖关子,答案是:异或运算。
看下面的代码:
public static void main(String[] args) {
int x = 10, y = 20; //定义两个变量
System.out.println("交换前 x=" + x + ",y=" + y);
x = x ^ y; //x = 30
y = x ^ y; //y = 10
x = x ^ y; //x = 20
System.out.println("交换后 x=" + x + ",y=" + y);
}
下面我们就来介绍一下异或运算。
异或运算
我们比较熟悉的逻辑运算,主要是「与运算」(AND)和「或运算」(OR),还有一种「异或运算」(XOR)。
XOR 是 exclusive OR
的缩写,一般使用插入符号(caret)^
表示。exclusive 意思是「专有的,独有的」,可以理解为 XOR 是更单纯的 OR 运算。
我们知道,OR 运算有两种情况,计算结果为 true。
- 一个为 true,另一个为 false
- 两个都为 true
上面两种情况,有时候需要明确区分,所以引入了 XOR。
XOR 排除了第二种情况,只有第一种情况才会返回 true,所以可以看成是更单纯的 OR 运算。也就是说, XOR 主要用来判断两个值是否不同。
如果约定 0 为 false,1 为 true,那么 XOR 运算规则:相同为 0,不同为 1
我们举个例子加深下印象。比如 a=8,b=9:
- 首先,我们把 a 和 b 转换成二进制,即
a=1000
,b=1001
- 然后 a 和 b 进行异或运算,一位一位的去对比,如果值相同,则对应位置异或运算的结果为 0,如果值不同,则为 1
运算过程如下:
1000 ^ 1001
1001
^ 1000
= 0001
异或运算的性质
(1)任何数 N 和自己进行异或运算,结果为 0
1001
^ 1001
= 0000
(2)0 和任何数 N 进行异或运算,结果为 N
1001
^ 0000
= 1001
(3) 异或运算满足交换律
x ^ y = y ^ x
(4) 异或运算满足结合律
x ^ (y ^ z) = (x ^ y) ^ z
再来看开头的例子
当你对异或运算有一定的了解了之后,咱们再来看一看开头的例子。
public static void main(String[] args) {
int x = 10, y = 20; //定义两个变量
System.out.println("交换前 x=" + x + ",y=" + y);
x = x ^ y; //x = 30
y = x ^ y; //y = 10
x = x ^ y; //x = 20
System.out.println("交换后 x=" + x + ",y=" + y);
}
第一步运算:
x = x ^ y;
第二步运算:
y = x ^ y;
我们合并下前两步的运算:
y = x ^ y ^ y
根据上述性质我们知道,「任何数 N 和自己进行异或运算,结果为 0」,所以 y ^ y 的结果就为 0,上面也就等价于:
y = x ^ 0
咱们在上面还说过,「0 和任何数 N 进行异或运算,结果为 N」,所以:
y = x ^ 0 = x
第三步运算:
x = x ^ y
此时 y 已经运算出来为 x,根据第一步运算赋值可知:x = x ^ y,所以第三步运算等价于:
x = x ^ y ^ x = 0 ^ y = y(运算细节同第二步)
应用
除了上面我们介绍到的「不使用临时变量交换两个数的值」,异或运算还有很多重要应用。
判断一个二进制数中 1 的数量是奇数还是偶数
利用异或的性质,可以让二进制数中每 1 位执行异或运算。结果为 1,则 1 的数量是奇数,若结果为 0,则 1 的数量是偶数。
以 0110 1100
为例,0 ^ 1 ^ 1 ^ 0 ^ 1 ^ 1 ^ 0 ^ 0 = 0
,可知 1 的数量为偶数。
加密
异或运算可以用于加密。
第一步,明文(text)与密钥(key)进行异或运算,可以得到密文(cipherText)。
text ^ key = cipherText
第二步,密文与密钥再次进行异或运算,就可以还原成明文。
cipherText ^ key = text
原理很简单,如果明文是 x,密钥是 y,那么 x 连续与 y 进行两次异或运算,得到自身。
只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
异或运算符满足交换律和结合律,所以假设有一个非空整数数组为:[A C B C B A D],把每一项进行异或运算:
A ^ C ^ B ^ C ^ B ^ A ^ D
= A ^ A ^ B ^ B ^ C ^ C ^ D
= 0 ^ 0 ^ 0 ^ D
= 0 ^ D
= D
示例代码:
public int SingleNumber(int[] nums) {
int res = nums[0];
for (int i = 1; i < nums.Length; i++){
res ^= nums[i];
}
return res;
}
一道面试题
给你一个包含有 n + 1 个元素的数组,其中每个数字在 [1, n] 的范围内,且 1 到 n 每个数字都会出现。也就是从 1 到 n 这 n 个数字,有一个数字在这个数组中出现了两次。编写一个算法,找到这个多余的数字。
示例:
输入: [1,3,4,2,2]
输出: 2
解析:如果使用异或解决的话,只需要首先计算出从 1 到 n 这 n 个数字的异或值,然后,再将数组中的所有元素依次和这个值做异或,最终得到的结果,就是这个多余的数字。
写成式子就是:
1 ^ 2 ^ 3 ^ … ^ n ^ A[0] ^ A[1] ^ A[2] ^ … ^ A[n]
。
具体的算法就留给各位自己来实现啦。
如果你还想看更多优质原创文章,欢迎关注我的公众号「ShawnBlog」。