与 &
与操作,二进制位上如果不相同则记为0:
1001 & 1101 =1001
保留右边第一个1,其他全部置为0
现有数字A = 100010001100100,需要保留右边第一个1,其他全部置为0:
先对A进行取反: ~A=011101110011011
取反后+1 :~A+1 = 011101110011100
再与A: (~A +1 )& A : 011101110011100
& 100010001100100
= 000000000000100
所以结果即为: A & (~A +1)
计算二进制中1的个数
public static void bitCount(int num) {
int count = 0;
// 假设 num = 10010
while (num != 0) {
// 第一次:rightFirst = 00010
int rightFirst = num & (~num + 1);
count++;
// 第一次:num(10010) ^ rightFirst(00010) = 10000,相当于抹除了最右边的1,然后进入下一个循环
num ^= rightFirst;
}
System.out.println(count);
}
异或^
我们来认识下异或操作,相信大家一定知道,二进制位,相同位,在^情况下,如果数值相同则为0,数值不同则为1:
1001 ^ 1010 = 0011
所以一个数,异或自身,一定为0,因为它所有二进制位的值都是一样的。
故而,如果A^ B ^ A = A ^ A ^B ,恒等于B
异或的结果,与参与异或的数据顺序无关。
交换数值如何不借用第三方变量
我们在进行排序操作时,很大的概率需要对数据进行交换操作,比如a[1]=6,a[3]=5,排序时需要将a[1]与a[3]的值进行交换,最终结果变成:
a[1] =5,a[3] = 6。我们如何在不借用第三方变量的情况下完全交换?
所以在交换的过程中可以这样写:
a[1] = a[1] ^ a[3] // 相当于a[1] = 6^5 , a[3]=5
a[3] = a[1] ^ a[3] // 相当于 a[3] =6^5^5 = 6 , 此时a[3] =6 , a[1]=6^5
a[1] = a[1] ^ a[3] // 相当于 a[1] = 6^5^6 =5 , 此时 a[1] =5,至此已经完成交换
找到唯一出现奇数次的那个数
假设现在有数据[1,2,1,3,2,4,5,5,4,3,3,…],具体长度不确定,现已知只有一个数字出现了奇数次,其他数字都是出现了偶数次,如何才能找到这个出现了奇数次的数?
根据我们前面介绍的原理,一个数 ^ 本身,一定是0。
假设现在1出现了偶数次,可能是2次,也可能是4次,也可能是6次,但是1 ^ 1=0,1 ^ 1 ^ 1 ^ 1=0,无论1出现了多少次,只要是偶数次,异或自身一定是0。
所以这道题目就比较简单了,定义一个变量num=0,依次遍历数据,将每一个数据都与num进行异或,如果这个数据出现的是偶数次,则异或结果一定还是0,如果这个数字出现了奇数次,那异或的结果一定是这个数本身:
num(0) ^ 1 ^ 2 ^ 1 ^3 ^ 2 ^4 ^5 ^5 ^4 ^ 3 ^ 3… =3,所以出现奇数次的数是3
public static void selectOddNumber(int[] array) {
int num = 0;
// num= a ^ b ,eg: num = 10000100
for (int i : array) {
num ^= i;
}
// a即为出现奇数次的那个数
int a = num;
}
找出出现奇数次的两个数
假设现在有数据[1,2,1,3,2,4,5,5,4,5,…],具体长度不确定,已知数据中有且仅有2个数字出现了奇数次,其他均为偶数次,需要将这两个数字找出来。
我们现在假设这两个出现奇数次的数分别是a和b,则
根据我们上面的解法,num(0) ^ 1^ 2^ 1 ^3 ^2 ^ 4 ^ …= a ^ b ,并且a ^ b 一定!= 0。
也就是说 a^b 的结果中,一定有某一位上是1。我们假设这个位数是第5位,那么原数据集合,就可以被重新划分为这样两类,集合1:二进制位数上第5位的值是1,集合2:二进制位数上第5位的值是0,且a和b一定分属于两个集合。假设a归属于集合1,b归属于集合2。现在我们只要在集合1中找到a,或者在集合2中找到b,根据前一步骤中得到的 a ^ b的值,便可以得到另一个数的值。
public static void selectOddNumberForTwo(int[] array) {
int num = 0;
// num= a ^ b ,eg: num = 10000100
for (int i : array) {
num ^= i;
}
// 找到a ^ b中最右边第一个是1的数据,eg:rightFirst= 00000100 在此例下,可以根据二进制第三位是否为1划分为两个集合
int rightFirst = num & (~num + 1);
int a = 0;
for (int i : array) {
if ((i & rightFirst) == 0) {
// 在所有第三位不为1的集合中,所有出现偶次数的值,在经过偶数次亦或之后,结果肯定是0,留下来的一定是那个奇数次的数
a ^= i;
}
}
int b = num ^ a;
}