算法之异或运算及其应用
基本介绍
异或算法又可称为无进位加法
1 ^ 1 = 0 ( 1 + 1 = 10 ,如果不进位的话,那结果就是0 )
1 ^ 0 = 1 ( 1 + 0 = 1 )
0 ^ 1 = 1 ( 0 + 1 = 1 )
0 ^ 0 = 0 ( 0 + 0 = 0 )
特性
满足交换律和结合律,表明计算结果和异或顺序无关
N ^ 0 = N N ^ N = 0
应用 - 快速交换值
1. 代码实现
交换 a 与 b 的值
private void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
2. 好处
按照上述方式进行值的交换,就无需开辟一个新的空间(不用创建一个变量来辅助进行值的交换)
3. 原理说明
需要使用到的知识为:
异或运算结果和顺序无关
N ^ 0 = N N ^ N = 0
说明如下:
譬如 a = 甲,b = 乙
① a = a ^ b => 此时 a = 甲 ^ 乙 , b = 乙
② b = a ^ b => 此时 a = 甲 ^ 乙 , b = 甲 ^ 乙 ^ 乙 = 甲 ^ 0 = 甲
③ a = a ^ b => 此时 a = 甲 ^ 乙 ^ 甲 = 甲 ^ 甲 ^ 乙 = 0 ^ 乙 = 乙 , b = 甲
通过分析,我们可以得到,经过这三步后, a 和 b 的值确实交换了
4. 使用前提
使用这个看似很高逼格的交换前提是 a 和 b 不能指向同一空间 ( 它们的值可以相等,但不能指向同一空间 ), 不然 a 和 b 就都会变成 0 ,因为此时 a ^ b = a ^ a = 0
你没办法确保传进来的两个值一定是不同的( 在排序算法及其应用2 - 冒泡排序中我们曾用过这个交换方法,是因为我们确定传进来的两个数肯定不是指向同一空间的 ),所以这种抖机灵的写法是不推荐的,日常最好还是按照我们一般的交换方式来写
算法题
1. 问题
有一个数组[]
① 数组中只有一种数字出现奇数次,其他数字都出现偶数次
② 数组中有两种数字出现奇数次,其他数字都出现偶数次
找到出现奇数次的数字
要求: 时间复杂度为 O(n), 空间复杂度为 O(1)
2. 思路
对于第 ① 种情况,所求的奇数次的数字即为数组中每个元素进行异或后的结果
证明如下:
由异或运算的特性:运算顺序不影响异或结果,所以出现偶数次的数字经过异或后就会变成0,所以结果自然只剩下出现奇数次的数字
举个栗子: 比如 arr = {1, 3, 9, 6, 6, 3, 3, 1, 3} [ 其中唯一出现奇数次的数字为 9 ]
将所有元素异或后得到 1 ^ 3 ^ 9 ^ 6 ^ 6 ^ 3 ^ 3 ^ 1 ^ 3 = 1 ^ 1 ^ 3 ^ 3 ^ 3 ^ 3 ^ 6 ^ 6 ^ 9 = 0 ^ 9 = 9
对于第 ② 种情况,会比第一种难一些,实现思路如下:
首先将数组中每个元素进行异或,得到的结果就是两个奇数次的数字的异或,将这个结果赋值给 eor
由于这两个数字不一样,所以 eor != 0,这两个数字的二进制肯定有一位是不同的
假设它们的第 8 位不同,即得到的异或结果的第 8 位为 1,我们让 eor' = “第 8 位为 1,其他都为 0”,而数组中所有元素可以分为两组, 一组为 ‘第 8 位为 0’,另一组为 ‘第 8 位为 1’,而我们所要求的这两个数分别在这两组中( 不可能为同一个组,因为上面已经假设他们第 8 位是不同的,所以必然一个为 1,一个为 0 ) ,并且每一组中除了要求的两个数,其他数都是偶数次的
我们将数组元素分别与 eor' 进行 & 运算,将第 8 位上等于 1 (或 0)的数字筛选出来,进行异或运算,这样就可以得到其中一个出现奇数次的数 a
再将 eor 与 a 进行异或,即可得到另外一个出现奇数次的数 b
听上去是不是有些抽象,那我们来举个栗子~
比如 arr = {1, 3, 9, 6, 6, 3, 3, 1, 3, 7} [ 其中唯一出现奇数次的数字为 9 和 7 ]
第一步,先将所有元素进行异或: eor = 1 ^ 3 ^ 9 ^ 6 ^ 6 ^ 3 ^ 3 ^ 1 ^ 3 ^ 7 = 1 ^ 1 ^ 3 ^ 3 ^ 3 ^ 3 ^ 6 ^ 6 ^ 9 ^ 7 = 0 ^ 9 ^ 7= 9 ^ 7 = 14
9 的二进制 1 0 0 1
7 的二进制 ^ 1 1 1
-----------------
1 1 1 0 ( 化为十进制就是14 )
第二步,我们看到 eor 的二进制结果中至少有一位不为 0,我们选出不为 0 的一位,譬如第 2 位, 那么 eor' = "第 2 位为1,其他都为 0" = 2
第三步,将 eor' 分别与数组元素进行 & 运算,就可以将元素分为了两组
1, 1 | 3, 3, 3, 3
9 | 6, 6
| 7
第 2 位为 0 第 2 位为 1
我们取与 eor' 异或结果为 0 的那一组(你想取结果为 0 或结果为 1 的都可以),即 第 2 位为 0 的那一组,将这组的元素进行异或运算,得到结果 a = 1 ^ 1 ^ 9 = 0 ^ 9 = 9
第四步,再将 a 与 eor 进行异或 ,即可得到结果 b = 9 ^ 14 = 7
a 和 b 即是我们要求的值
3. 代码
问题 ① 的代码
public int getOneNum(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= i;
}
return eor;
}
问题 ② 的代码
public void getTwoNum(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= i;
}
// eor = 所求两个数的异或结果
// eor 一定不为0, eor 必然有一位上是 1
int rightOne = eor & (~eor + 1); // 提取出最右位的1
int onlyOne = 0; // eor'
for (int cur: arr){
if ((cur & rightOne) == rightOne) { // 这里目的是为了分组, == 0 也是可以的
onlyOne ^= cur;
}
}
System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
说明:其中 int rightOne = eor & (~eor + 1); // 提取出最右位的1
用于得到某个数最右边位置上的1,是一种常规操作,要学会使用
欢迎大家来我博客逛逛 mmimo技术小栈