认识异或运算

389 阅读1分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。

一、认识异或运算

^ 相同为0,不同为1:可以理解为无进位相加

异或的特性:

  • 0 ^ N = N
  • N ^ N = 0
  • 满足交换律(a^b = b^a)和结合律((a^b)^c) = a^(b^c))

异或运算.png

偶数个1相加一定为0,奇数个1相加一定为1

二、异或运算练习

  1. 如何不用额外变量交换两个数

    a = a ^ b
    b = a ^ b
    c = a ^ b
    

    异或交换证明.png

  2. 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

    • 利用哈希表,每一种数做词频统计,看哪种数出现了奇数次,需要额外的空间复杂度
    • 利用异或运算,遍历一遍,只需要一个有限的变量就能找到哪种数出现了奇数次
    // arr中,只有一种数,出现奇数次
    public static void printOddTimesNum1(int[] arr) {
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        System.out.println(eor);
    }
    
  3. 怎么把一个int类型的数,提取出最右侧的1来

    提取最右侧的1.png

    a & (~a + 1) = a & (-a)

    a &(取反 + 1)等于 a & 相反数

    取反加1就是自己的相反数

    a & (~a + 1) = a & (-a)
    
  4. 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

    分析:假设a、b这两种数出现了奇数次,则a≠b,从头到尾遍历一遍进行异或,则eor = a^b,因为a不等于b,则eor一定不等于0,说明eor在某个位置上一定有1的状态,找哪个1位置呢,哪个位置都行,那就提取最右侧的1,假设在某个位置上是最右侧的1说明a,b在这个位置上一定是不一样的,因为一样的话,异或则为0。假设a最右侧位置上有1,b则最右侧位置上为0,怎么把a和b分开,rightOne :00000000000001000,依次判断每个数&rightOne,不等于0,则说明这个位置上有1,又因为偶数异或为0,所以左右就找到其中一个奇数,那么另外一个奇数则再异或下。

    找出奇数次的两种数分析.png

    // arr中,有两种数,出现奇数次
    public static void printOddTimesNum2(int[] arr) {
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        // a 和 b是两种数
        // eor != 0
        // eor最右侧的1,提取出来
        // eor :     00110010110111000
        // rightOne :00000000000001000
        int rightOne = eor & (-eor); // 提取出最右的1
        int onlyOne = 0; // eor'
        for (int i = 0; i < arr.length; i++) {
            //  arr[1] =  111100011110000
            // rightOne=  000000000010000
            if ((arr[i] & rightOne) != 0) {
                onlyOne ^= arr[i];
            }
        }
        System.out.println(onlyOne + " " + (eor ^ onlyOne));
    }
    
  5. 一个数组中有一种数出现K次,其他数都出现了M次,M > 1, K < M,找到出现了K次的数,要求额外空间复杂度O(1),时间复杂度O(N)

    分析:额外空间复杂度O(1),堵死了哈希表,可以利用长度为32的int数组,所有的数换算成二进制,所有数字二进制上的每个位置上的1全累加到数组中,

    // 请保证arr中,只有一种数出现了K次,其他数都出现了M次
    public static int onlyKTimes(int[] arr, int k, int m) {
        int[] t = new int[32]; // 额外空间复杂度O(1)
        // t[0] 0位置上的1出现了几个
        // t[i] i位置上的1出现了几个
        for (int num : arr) { // O(N)
            for (int i = 0; i <= 31; i++) { // 固定次数
                t[i] += (num >> i) & 1;
                // if (((num >> i) & 1) != 0) {
                //     t[i]++;
                // }
            }
        }
        int ans = 0; // 干干净净,往上填答案,构建答案
        for (int i = 0; i < 32; i++) { // 固定次数
            if (t[i] % m != 0) { // 说明k在i位上,有1
                ans |= (1 << i);
            }
        }
        return ans;
    }
    

三、总结

异或也称为无进位相加

0 ^ N = N

N ^ N = 0

提取最右侧的1:a ^ (-a)