算法 - 运算符

与 &

与操作,二进制位上如果不相同则记为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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值