异或运算 ^:相同为0,不同为1【无进位相加】
与运算 &:相同为1,不同为0
或运算 |:有1为1
异或运算的性质
- 0^N == N N^N == 0
- 异或运算满足交换律和结合律
1. 0^N == N
一个数对0进行异或,则结果为自身
如: 0 ^ 5 = 5. 0 ^ 1 = 1
0000 ^ 0101 = 0101
2 N^N == 0
一个数对自己进行异或,则结果为0
如:5^5 = 0
0101 ^ 0101 = 0000
3 异或运算满足交换律和结合律
a^b^c = a^(b^c) = a^c^b
结合使用比如:
3^5^2^5^3 = (3^3)^(5^5)^2 = 0^0^2 = 2
例子
1 如何不用额外变量交换两个数
java
// a 值为 甲, b值为 乙
a = a^b // a= 甲^乙, b= 乙
b = a^b // a= 甲^乙, b= 甲^乙^乙 = 甲
a = a^b // a= 甲^乙^甲 = 乙, b= 甲
示例:
java
public static void main(String[] args) {
int[] arr = {4,7,244,5,2};
int i= 1,j=2;
swap(arr,i,j);
System.out.println(arr[i]+","+arr[j]);
}
//i 与 j 不能相同
//对于引用来说,如果两个指针的地址指向同一个内存地址,那两个相同异或则会为0
public static 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];
}
//结果
244,7
2 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数
思路一:
使用hashmap,遍历数组,key为数组值,value为计数,最后遍历hashmap,找到基数value,返回key为该数。
思路二:
相同数进行异或值为0,0与其他数异或值为其他数。
定一个变量 eor = 0,分别与数组中每一个数进行异或
相当于:
0 ^ arr[0] ^ arr[1] ... arr[n-1]
由于数组是特殊的,只有一种数出现基数次,其余为偶数次,那么偶数次相同数进行异或都为0,最后剩下一个基数次的数
java
public static void printOddTimesNum1(int[] arr){
int eor = 0;
for(int a:arr){
eor ^= a;
}
System.out.println(eor);
}
public static void main(String[] args) {
int [] arr = {1,4,3,4,2,1,3};
printOddTimesNum1(arr);
}
//结果
2
3 怎么把一个int类型的数,对于二进制 提取出最右侧的1来
思路: a & (-a)
与&运行:相同为1
-a 在二进制中表示为 取反 + 1
bash
a = 00110101000
# -a = a取反加1
-a = ~a + 1
-a = 11001010111 + 1 = 11001011000
所以
a = 00110101000
-a = 11001011000
a & -a = 00000001000
4 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数
思路:
首先参照2,先将偶数次的数消掉,即 eor = eor ^ arr,之后得到的就是两个基数进行异或的结果,假设这两个出现基数的数为a和b,这eor = a^b,并且因为a 不等于 b,所以eor也不等于0
此时,eor为 a ^b,由于a 不等于b,观察eor的二进制形式,其中有1的位一定为a和b不相同的位置【因为异或为不同位为1,相同位为0】,指定最右侧为1位作为分界线,计算得rightOne,
假设最右侧位为第3位,则数组中的数可以分为两类,一类位第3位为1,记为左侧,一类位第3为为0,记为右侧,且a与b一定不在同一侧,假设a在左侧,b在右侧
接着,即eor2 = 0,异或左侧数据,则得到 a,此时eor2 = a【左侧中只有一个是基数项为a,其他偶数项被消掉 】
得到a后,b = eor ^ eor2[此时为eor = a ^b, eor2 = a,b = a^b ^a =b]
举例:
arr = [6,10,4,4,12,12,12,12,3,3] //此时出现基数次的为6,10
6 = 00110
10= 01010
eor = 6 ^ 10 = 01100
得到最右侧为1的数为00100
划分左侧即第三位为1的数有6,4,12,第三位为0的数有10,3
eor2 = 0 ^ 6 ^4^4^12^12^12^12 = 6
b = eor ^ eor2 = 6^10^6 = 10
得到两个基数次的数为6和10
code:
java
public static void printOddTimesNum2(int[] arr) {
int eor = 0;
for(int x:arr){
eor ^= x;
}
//此时eor = a ^ b
//最右侧为1
int rightOne = eor & -eor;
int eor2 = 0;
//查找ringthOne位为1的进行异或操作,得到a
for (int x : arr){
if((x & rightOne) != 0){
eor2 ^= x;
}
}
int a = eor2;
int b = eor ^ eor2;
System.out.println(a+","+b);
}
public static void main(String[] args) {
int [] arr1 = {6,10,12,12,12,12,4,4,3,3};
printOddTimesNum2(arr1);
}
//结果
//6,10
5 km次
一个数组中有一种数出现K次,其他数都出现了M次,
M > 1, K < M
找到,出现了K次的数,
要求,额外空间复杂度O(1),时间复杂度O(N)
思路:
准备一个长度为32的整形数组 t,遍历arr,将每一个元素的所以位为1的位置统计到t中,如元素a的第3位为1,则t[2]++,第9位为1,则t[8]++
即
t[0] 0位置的1出现了几个
t[i] i位置的1出现了几个
对t数组每一个元素,如果对 M取模不为0,则说明出现k次的数在该为为1【因为如果该位能被7整除,说明该位的1出现次数为整数次】
设置一个变量ans = 0,遍历t,如果元素对M取模不为0,t[i] %M != 0,则 ans的第i位设置为1
如何将第i位设置到ans上呢
将1左移i位,则i位置位1,其余位置为0,再 或运算到ans上,i位置变为1,其余位置保持不变
code:
java
public static void km1(int [] arr,int k,int m){
int [] t = new int[32];
//遍历arr数组
for (int x : arr){
//对每一元素的每一位检查,如果为1则t[i]计数增加1
for(int i=0;i<32;i++){
//x右移i位则将待检查位置移动到最右侧,与1进行与运算,如果该位置为1,则结果为1,如果该位置为0则结果为0
if((x >> i & 1) != 0){
t[i]++;
}
}
}
int ans = 0;
//对t数组计算
for (int i = 0; i < 32; i++) {
//如果出现次数无法被m整除,说明出现k次的数在该位出现
if(t[i] % m != 0){
//则将该位记录,最终32位统计完成后,则为该数
// 1 左移i位添加到ans中
ans |= 1 << i;
}
}
System.out.println(ans);
}
public static void main(String[] args) {
//k = 2,m = 4
int [] arr = {6,6,10,10,10,10,7,7,7,7,11,11,11,11,0,0,0,0,-1,-1,-1,-1};
km1(arr,2,4);
}
//结果
// 6