位运算的秒用:异或运算

今天闲话不用多说,先来看一个小例子。

如何交换两个变量的值(整数)?

问题很简单,交换两个数,常规的做法是引入一个中间变量,代码如下:

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」。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值