异或运算
引入:
袋子里一共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; }
-
-