再聊异或

再聊异或

一、异或运算

一、运算法则

异或是一种基于二进制的位运算,用符号XOR或者 ^ 表示,其运算法则是对运算符两侧数 ( 2 ^ 1 ) 的每一个二进制位,同值取0,异值取1。

就是把两个数先转换为二进制数。再对照位来进行运算,相同为0,不同为1。

例如: 2 ^ 1

分析: 2 转换为二进制为10

​ 1 转换为二进制位 1,为了与 2 的二进制进行对照位运算,这里写为 01

则根据运算法则可知

							   10
                 2  ^   1 =           =  11     = 3        (转化为十进制数)                       
                               01
                               10     
                 2  ^  0 =             =   10  =  2       (这里可知,任何数与0异或,都等于它本身)
                               00
                               01 
                 1  ^  1  =            =  00    =   0     (这里可知,两个相同的数异或,结果为0)
                               01

二、异或运算的规律

  • 对于任何数 x,都有 x ^ x = 0,x ^ 0 = x,同自己求异或为0,同0求异或为自己。
  • 自反性 A ^ B ^ B = A ^ 0 = A ,连续和同一个因子做异或运算,最终结果为自己。

三、异或运算的作用

到这里,异或运算的基本运算法则和一些简单的规律已经清楚。但是学异或,有什么用呢?

作用一:交换两数

都知道,交换两个变量值可以通过引入一个中间量来进行两个变量值的交换。这也是常规的、常用的方法。但是,不让你用中间量呢?可能有人思考一下会想出一下方案:

int a = 4;
int b = 6;
a = a + b;    //  此时  a = 10;
b = a - b;    //  此时  b = 4;
a = a - b;    //  此时  a = 6;
System.out.println("a的值为:" + a);
System.out.println("b的值为:" + b);

通过以上方法也可以交换两个变量的值。但是,还是不满意,加加减减,小孩子都可以做。于是,异或运算,它来了!

int a = 4;  // a 的二进制数为 :100
int b = 3;  // b 的二进制数为 :011
a = a ^ b;  // 此时 a = 111
b = a ^ b;  // 此时 a ^ b =  111 ^ 011  = 100   此时 b = 100 = 4
a = a ^ b;  // 此时 a ^ b =  111 ^ 100  = 011    此时 a = 011 = 3
System.out.println("a的值为:" + a);
System.out.println("b的值为:" + b);

作用二:判断奇数偶数

由于异或运算要先转化为二进制数,所以就决定了偶数的二进制数的最后一位必定是0,奇数的二进制数的最后一位必定是1。

所以,与 1 异或,得到的结果 - 要判断的数 等于 1 ,说明该数是偶数,反之是奇数!(也可以是 :要判断的数 - 异或得到的结果 == 1是奇数,刚好相反,就不再多说! )

int a = 43234536;
if ((a ^ 1) - a == 1) {
	System.out.println("是偶数!");
} else {
	System.out.println("是奇数!");
}

作用三:判断两数是否相等

根据规律可知,对于任何数 x,都有 x ^ x = 0。所以,若两数异或结果为0,则两数相等。这个不是主要作用,就不在过多叙述!

作用四:简单的数据加密(可以但没必要)

在进行数据的存储时,不对数据进行加密,都是以明文形式存储在数据库中,例如以下表:

image-20200819133410946

image-20200819133423045

所以就存在数据不安全。所以一般在存储到数据库会对敏感数据进行加密。这里,介绍一种简单的加密:使用异或进行加密。

public static void main(String[] args) {
        String a ="asdvfff";
        String b= security(a);
        System.out.print("加密后数据为:");
        System.out.println(b);
        String c= security(b);
        System.out.print("解密后数据为:");
        System.out.println(c);
    }

    public static String security(String str) {
        char [] chs =str.toCharArray();
        for(int i = 0;i < chs.length; i++){
            chs[i]=(char)(chs[i] ^ 20000);
        }
        return new String(chs);
    }

作用五:找出那个唯一成对的数

1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现 一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空 间,能否设计一个算法实现?

解法一:显然已经有人提出了一个比较精彩的解法,将所有数加起来,减去(1+2+...+1000)的和。 这个算法已经足够完美了,相信出题者的标准答案也就是这个算法,唯一的问题是,如果数列过大,则可能会导致溢出。

解法二:异或。异或就不存在溢出等问题。

​ 将所有的数全部异或(121000k,k为重复数),得到的结果与(1231000)的结果进行异或,得到的结果就是重复数。 即(1^2^...^1000^k)^(1^2^3^...^1000),由于有结合律、交换律存在,我们可以这么调整为(1^1^2^2^...^1000^1000^k)那结果当然就是k了。详细代码如下:

int[] a = {1,3,4,5,2,6,7,8,9,2,10};
int b = 0;
for (int j = 0; j <= a.length; j++) {
	if (j < a.length-1) {
		b = b ^ a[j] ^ (j+1);
	}
	if (j == a.length) {
		b = b ^ a[j-1];
	}
}
System.out.println(b);

作用六:上例变形,找出出现奇数次的数

面试题: 一个数组存放若干整数,一个数出现奇数次,其余数均出现偶数次,找出这个出现奇数次的数?

使用异或也可以轻松解决。因为这个数组里只有一个数出现了奇数次,其余数字都出现了偶数次,又因为异或有交换律。所以我们只需一次异或这个数组里的数,那些出现偶数次的数都会相互异或为0,得到的最终结果即为出现奇数次的数。详细代码如下:

int[] a = {1,1,2,2,3,3,4,4,5,5,2};
int b = 0;
for (int i = 0; i < a.length; i++){
	b = b ^ a[i];
}
System.out.println(b);

再次变形:找出只出现一次的数,其余数都出现偶数次!

与上述思路一致,感兴趣可以自己尝试一下!

作用七:难度der~一下子上来了!

题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

思考:两数相同,异或结果为什么是0.是因为它们转换为二进制数相同,所以异或结果为0;所以,当异或结果不为0,则说明那两个数的二进制数至少有一个位上不相同。

思路:按照上诉套路,一次异或,最终结果是那两个不相等的数的异或结果!那么,有没有办法将它们两个分开呢?首先思考,它两的异或结果不为0,那么它们转换为二进制数,一定有在某位上不相等。所以才出现了异或结果不为0.所以,我们只要找到这一位位数。把原数组按此位分为两部分,再分别对这两个子数组依次异或。每个子数组异或得到的结果就是那个只出现了一次的数。

用到的知识:
1、与(&)运算:两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。

  * 例如 : 4 & 5 =  100 & 101 = 100 =  4
           3 & 1 =  1 1 & 0 1 = 0 1 =  1
  * 所以和 1 进行 与运算,如果结果为1,则说明该数的最低位为1。

2、右移(>>)运算:该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。

例如 : 6 >> 1

​ 右移之前:110 ------> 6

​ 右移之后:0110 ------> 3

负数右移:

-5
原码 1000 …… 0101
反码 1111 …… 1010 负数的反码是保留符号位不变源码取反
补码 1111 …… 1011 补码是反码加1
>>2 (负数右移高位补1)
补码 1111 …… 1110
反码 1111 …… 1101 补码转反码减1
源码 1000 … 0010 负数反码转源码保留符号位不变取反
= -2

  • 难点1:怎么找到异或结果某位为 1 在第几位呢?

​ 或许会有好几个1 ,但我们找到 1 位即可。这时候用右移运算,不停的将异或结果的二进制数右移,再与 1 进行与运算。如 果结果为 1 ,那么这一位一定为 1 .

  • 难点2:找位数要循环多少次呢?或者为什么代码里循坏32次?
    此题只针对int型数组,int占 4 个字节,所以有32位,最大数为 2^31(这里不是异或,是 2 的 32 次)

上代码:

int [] arr = {1,1,2,2,3,3,4,9};
//第一次循环异或数组的初始量
int b = 0; 
//接收那两个不一样的数
int[] result = new int[2];
for (int j : arr) {
	b = b ^ j;
}
int index = 0;
for (int j = 0; j < 32; j++) {
	if ((b >> j & 1) == 1) {
		index = j;
		break;
	}
}
//根据index为1,将元素分为两组
for(int i=0; i<arr.length; i++) { 
    //对应位为1,异或得到的结果
	if((arr[i] >> index & 1) == 1) {
		result[0] ^= arr[i];
	} 
    //对应位为0,异或得到的结果
    else{
		result[1] ^= arr[i];
	}
}
for (int res : result) {
	System.out.println(res)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值