异或运算

异或运算

引入:

袋子里一共a个白球,b个黑球,每次从袋子里拿2个球,每个球每次被拿出机会均等

如果拿出的是2个白球、或者2个黑球,那么就往袋子里重新放入1个白球

如果拿出的是1个白球和1个黑球,那么就往袋子里重新放入1个黑球

那么最终袋子里一定会只剩1个球,请问最终的球是黑的概率是多少?用a和b来表达这个概率。

答案:

黑球的数量如果是偶数,最终的球是黑的概率是0%

黑球的数量如果是奇数,最终的球是黑的概率是100%

思路:

  • 将白球视为0,黑球视为1

  • 任何个0异或, 结果一定为0; 奇数个1异或, 结果为1; 偶数个1异或, 结果为0

  • 2白2黑异或: 0 ^ 0 ^ 1 ^ 1 == 0 (白)

  • 1白1黑异或: 0 ^ 1 == 1(黑)

  • ---> 结果可以类比为: a个1和b个0异或

    • 当b为偶数, 结果为0(全白)

    • 当b为奇数, 结果为1(全黑)

1. 异或运算的性质

1)异或运算就是无进位相加

2)异或运算满足交换律、结合律,也就是同一批数字,不管异或顺序是什么,最终的结果都是一个

3)0 ^ n=n,n ^ n=0

4)整体异或和如果是x,整体中某个部分的异或和如果是y,那么剩下部分的异或和是x^y

  • x = a ^ b y = a -> b = a ^ b ^ a (a,b为部分异或和)

2. 相关题目

  • 题目1 交换两个数

    • 前提: 两个数位于不同的内存地址, 否则: a = arr[0] ^ arr[0] = 0(两值相同时会出错, 故知道该写法即可, 并不推荐)

    • int a = x,b = y;
      a = a ^ b;// a = x ^ y
      b = a ^ b;// b = x ^ y ^ y = x
      a = a ^ b;// a = x ^ y ^ x = y
      ---> a == y, b == x

  • 题目2 不用任何判断语句和比较操作,返回两个数的最大值

    • 测试链接 : 获取最大值_牛客题霸_牛客网

    • 思路

      • 使用异或, c = a - b , 返回a的条件: a b 同号时,c > 0; a b 异号时, a < 0

      • 当a b异号,c可能溢出, 因此需要转化为对a的判断

    • 代码

      •     // 必须保证n一定是0或者1
            // 0变1,1变0
            public static int flip(int n) {
                return n ^ 1;// 返回0/1取反
            }
        ​
            // 非负数返回1
            // 负数返回0
            public static int sign(int n) {
                return flip(n >>> 31);// 无符号右移: 把符号位移动到最右侧, 返回符号位取反
            }
        ​
            // 添加了对ab符号的判断
            public static int getMax(int a, int b) {
                
                int c = a - b;// c可能是溢出的
                
                int sa = sign(a);// a的符号
                int sb = sign(b);// b的符号
                int sc = sign(c);// c的符号
                
                // 判断A和B,符号是不是不一样,如果不一样diffAB=1,如果一样diffAB=0
                int diffAB = sa ^ sb;
                // 判断A和B,符号是不是一样,如果一样sameAB=1,如果不一样sameAB=0
                int sameAB = flip(diffAB);
                
                int returnA = diffAB * sa + sameAB * sc;// 返回a: ab符号不同 且 a>0; ab符号相同 且 c > 0
                int returnB = flip(returnA);// 不返回b时就返回a
                return a * returnA + b * returnB;
            }

  • 题目3 找到缺失的数字

    • 测试链接 : . - 力扣(LeetCode)

    • 思路

      • 根据异或性质4, 缺失的数为所有数的异或和 异或 没有缺失的数的异或和

    • 代码

      • public static int missingNumber(int[] nums) {
                int eorAll = 0, eorHas = 0;
                for (int i = 0; i < nums.length; i++) {
                    eorAll ^= i;
                    eorHas ^= nums[i];
                }
                eorAll ^= nums.length;
                return eorAll ^ eorHas;
            }

  • 题目4 数组中1种数出现了奇数次,其他的数都出现了偶数次,返回出现了奇数次的数

    • 测试链接 : . - 力扣(LeetCode)

    • 思路

      • 全部跟0进行异或, 消除偶数个相同的数, 留下的就是那个奇数次的数

    • 代码

      •     public static int singleNumber(int[] nums) {
                int eor = 0;
                for (int num : nums) {
                    eor ^= num;
                }
                return eor;
            }

  • Brian Kernighan算法 - 提取出二进制状态中最右侧的1

    • int rightOne = eor1 & (-eor1);

  • 题目5 数组中有2种数出现了奇数次,其他的数都出现了偶数次,返回这2种出现了奇数次的数

    • 测试链接 : . - 力扣(LeetCode)

    • 思路

      • 首先用0异或所有数, 得到 a ^ b的结果, 由于 a != b, 结果中至少含有一位1

      • 根据最右侧的1, 将所有数分为两类, 该位为1 和 该位为0 的, 然后找其中一侧与0异或, 得到a 或 b

      • 用异或总和(a ^ b) 异或 第二次的异或和(a 或 b), 得到另一位数

    • 代码

      •     public static int[] singleNumber(int[] nums) {// nums中有2种数a、b出现了奇数次,其他的数都出现了偶数次
                int eor1 = 0;
                for (int num : nums) {
                    eor1 ^= num;// eor1 : a ^ b, 由于a b不同, eor1中至少有一位为1
                }
                
                // Brian Kernighan算法
                // 提取出二进制里最右侧的1
                int rightOne = eor1 & (-eor1);// !!! &
                
                int eor2 = 0;
                for (int num : nums) {
                    // 假设rightOne = 0000 0100 则 rightOne只会与和最右侧1位置是0的相异或,即只与a/b异或
                    if ((num & rightOne) == 0) {// 或!=0, 不能用==1 : 比如0000 0111 -> 0000 0100 == 4 != 1
                        eor2 ^= num;
                    }
                }
                return new int[] { eor2, eor1 ^ eor2 };
            }

  • 题目6 数组中只有1种数出现次数少于m次,其他数都出现了m次,返回出现次数小于m次的那种数

    • 测试链接 : . - 力扣(LeetCode)

    • 思路

      • 将数组中每一个数的每一位拆分出来, 相加, 放入cnts数组, 用以统计所有数中每一位1的个数

      • 遍历cnts数组, 将每一个数 % m(其他数在数组中出现的个数), 不为0的那位说明在该位上所求数为1, 为0的说明在该位上所求数为0

      • 将为1的每一位与ans异或, 放入ans中, 最终返回ans

    • 代码

      •     public static int singleNumber(int[] nums) {
                return find(nums, 3);
            }
        ​
            // 更通用的方法
            // 已知数组中只有1种数出现次数少于m次,其他数都出现了m次
            // 返回出现次数小于m次的那种数
            public static int find(int[] arr, int m) {
                // cnts[0] : 0位上有多少个1
                // cnts[i] : i位上有多少个1
                // cnts[31] : 31位上有多少个1
                int[] cnts = new int[32];
                for (int num : arr) {// 遍历每一个数,计算这些数每一位上1的个数
                    for (int i = 0; i < 32; i++) {
                        cnts[i] += (num >> i) & 1;// 统计每一位上1的个数
                    }
                }
                
                int ans = 0;
                for (int i = 0; i < 32; i++) {
                    if (cnts[i] % m != 0) {
                        // 若第i位出现1的次数不为m的倍数, 则所求数在该位上一定为1
                        // 若第i位出现1的次数为m的倍数, 则所求数在该位上一定为0
                        ans |= 1 << i;// (ans = ans | (1 << i)) 将不为0的那一位的1或进ans里
                    }
                }
                return ans;
            }

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值