异或在算法中的妙用
异或的性质
- 与0异或为自己,与自己异或为0
x ^ 0 = x;
x ^ x = 0;
- 满足交换律和结合律
x ^ y = y ^ x;
x ^ y ^ z = x ^ (y ^ z);
由以上两个性质可以得知:
- 异或运算的结果与参与异或运算的变量的排列组合无关,即与运算的先后顺序无关
用异或来实现两个数值交换
例如:
int a = 11;
int b = 24;
a = a ^ b; //该句执行完,得到: a = 11 ^ 24; b = 24;
b = a ^ b; //得到: a = 11 ^ 24; b = 11 ^ 24 ^ 24 = 11 ^ 0 = 11;
a = a ^ b; //得到: a = 11 ^ 24 ^ 11 = 0 ^ 24 = 24; b = 11;
//可见最终 a = 24; b = 11; 实现了交换
代码如下:
void swap(int a, int b){
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
注意的点
- 上述示例中的
a
和b
必须是内存地址不同的值。
例如:
我们来交换一个数组中的两个值:
void swap(int arr[], int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
若 i
和 j
指向了数组中的同一处,最后该处结果为 0
- 对于C++中基本类型,只适用于
int
char
bool
wchar_t(short int)
- 所以对于复杂数据的交换,不推荐使用异或,可以用引用或者指针来做;
- 还有一点,用异或不见得比定义 temp 传值更“快”,可参考博客:
异或交换真的比开一个tmp快吗?_Lawliet-CSDN博客
异或运算的例题
对于一个数组:
- 已知只有一种数(记为
a
)出现了奇数次,其他的所有数出现了偶数次,求a
- 已知有两种数(记为
a
、b
)出现了奇数次,其他的所有数出现了偶数次,求a
、b
要求时间复杂度 O(N),空间复杂度 O(1)。
-
第一种情况:
很容易想到将数组中的所有值异或运算,最后的结果即为
a
-
第二种情况:
例如:
int arr[] = { 1,1,1,2,3,3,4,4,5,5 };
那么两个数就是 1 和 2
- 同第一种情况,先将所有的数异或,最后得到结果记为
eor
,显然eor = a ^ b
,且eor ! = 0
; - 对于
eor
,其底层01流中肯定不全为 0,我们假设第k
位为 1 ,那么对于a
、b
第k
位一个是1,另一个是0; - 显然,除却
a
或b
,对于数组中 第k
位是 1(或者0) 的元素,它们是出现了偶数次; - 将 第
k
位是 1(或者0) 的元素进行异或,最后得到结果记为eor1
,那么eor1 = a
或者是eor1 = b
,总能得到a
、b
中的一个; - 最后将
eor1
和eor
异或 得到另一个。
代码如下:
void eorSolution(int arr[], int size) { int eor = 0,eor1 = 0; for (int i = 0; i < size; i++) { eor ^= arr[i]; } int rightOne = eor & (~eor + 1); for (int i = 0; i < size; i++) { if ((arr[i] & rightOne) == 0) { eor1 ^= arr[i]; } } cout << eor1 << " " << (eor ^ eor1) << endl; }
🔴 其中需要注意的是,提取出某数01流中最后出现的 1
int rightOne = eor & (~eor + 1);
原理是 将数 取反、+1、与原值与运算,前提是该数不为 0
- 同第一种情况,先将所有的数异或,最后得到结果记为
🎸写在后面:
- 算法部分需要画图理解,但是我还没找打合适的方式,画图部分后续会练习的。
- 博客主要是为了让自己理解记忆,盘清逻辑,所以没有举例讲解,见谅啦😄
- 学无止境,后续待改进……