异或运算相关应用

异或运算性质

1)异或运算就是无进位相加(这样记相对方便,不会忘)

比如3^5,就是0011 ^ 0101 =  0110

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

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

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

比如a^b = c,那么b = a^c,a=b^c

这些结论最重要的就是1)结论,所有其他结论都由这个结论推论得到,4)结论用到的最多。

根据异或运算的这些结论,我们来看一个好玩的问题:

       袋子里一共a个白球,b个黑球,每次从袋子里拿2个球,每个球每次被拿出机会均等。如果拿出的是2个白球、或者2个黑球,那么就往袋子里重新放入1个白球;如果拿出的是一个白球和一个黑球,那么就往袋子里重新放入一个黑球。那么最终袋子里一定只剩一个球,请问最终的球是黑的概率是多少?用a和b来表达这个概率。

        解:我们设黑球是1,白球是0,那么从袋子里拿出一个黑球和一个白球,再放回一个黑球,就相当于1^0 = 1;拿出的是2个白球、或者2个黑球,那么就往袋子里重新放入1个白球,就相当于1^1=0,0^0=0,这样就很显然了。

        那么从袋子里一直拿2个球在放回一个球,就可以类似于袋子里的0和1一起做异或运算的过程,因此,当黑球为偶数个数时,所有0和1做异或运算,答案为0,最终袋子里百分百为白球;当黑球为基数个数时,所有0和1做异或运算,答案为1,最终袋子里百分百是黑球。袋子里最终留下什么球,和白色球的个数无关。

异或运算应用1:交换两个数的数值

// 用异或运算交换两数的值
public class Code01_SwapExclusiveOr {

	public static void main(String[] args) {
		int a = -2323;
		int b = 10;
		a = a ^ b;
		b = a ^ b;
		a = a ^ b;
		System.out.println(a);
		System.out.println(b);

		int[] arr = { 3, 5 };
		swap(arr, 0, 1);
		System.out.println(arr[0]);
		System.out.println(arr[1]);
		swap(arr, 0, 0);
		System.out.println(arr[0]);
	}

	// 当i!=j,没问题,会完成交换功能
	// 当i==j,会出错
	// 所以知道这种写法即可,并不推荐
	public static void swap(int[] arr, int i, int j) {
		arr[i] = arr[i] ^ arr[j];
		arr[j] = arr[i] ^ arr[j];
		arr[i] = arr[i] ^ arr[j];
	}

}

异或运算应用2:不用任何判断语句和比较操作,返回两个数的最大值

获取最大值_牛客题霸_牛客网

public class Code02_GetMaxWithoutJudge {

	// 必须保证n一定是0或者1
	// 0变1,1变0
	public static int flip(int n) {
		return n ^ 1;
	}

	// 非负数返回1
	// 负数返回0
	public static int sign(int n) {
		return flip(n >>> 31);    //无符号右移,直接把符号位的数字移到第一位
	}

	// 有溢出风险的实现
	public static int getMax1(int a, int b) {
		int c = a - b;
		// c非负,returnA -> 1
		// c非负,returnB -> 0
		// c负数,returnA -> 0
		// c负数,returnB -> 1
		int returnA = sign(c);
		int returnB = flip(returnA);
		return a * returnA + b * returnB;
	}

	// 没有任何问题的实现
	public static int getMax2(int a, int b) {
		// c可能是溢出的
		int c = a - b;
		// a的符号
		int sa = sign(a);
		// b的符号
		int sb = sign(b);
		// c的符号
		int sc = sign(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;
		int returnB = flip(returnA);
		return a * returnA + b * returnB;
	}

	public static void main(String[] args) {
		int a = Integer.MIN_VALUE;
		int b = Integer.MAX_VALUE;
		// getMax1方法会错误,因为溢出
		System.out.println(getMax1(a, b));
		// getMax2方法永远正确
		System.out.println(getMax2(a, b));
	}

}

异或运算应用3:找到缺失的数字

268. 丢失的数字 - 力扣(LeetCode)

public class Code03_MissingNumber {

	public static int missingNumber(int[] nums) {
		int eorAll = 0, eorHas = 0;
		for (int i = 0; i < nums.length; i++) {
			eorAll ^= i;    //异或0-n-1中所有数字
			eorHas ^= nums[i];    //异或nums数组中已有数字
		}
		eorAll ^= nums.length;
		return eorAll ^ eorHas;    //根据结论4)得到结果
	}

}

异或运算应用4:数组中1种数出现了奇数次,其他的数都出现了偶数次,返回出现了奇数次的数

这题运用结论3轻松解决

136. 只出现一次的数字 - 力扣(LeetCode)

public class Code04_SingleNumber {

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

}

异或运算应用5:数组中有2种数出现了奇数次,其他的数都出现了偶数次,返回出现了奇数次的数

260. 只出现一次的数字 III - 力扣(LeetCode)

在这题中,将设这2种出现奇数次的数分别为a,b,将数组中所有的数做异或运算,得到的答案为a^b,如果分别得到a和b的值,这是个问题

public class Code05_DoubleNumber {

	public static int[] singleNumber(int[] nums) {
		int eor1 = 0;
		for (int num : nums) {
			// nums中有2种数a、b出现了奇数次,其他的数都出现了偶数次
			eor1 ^= num;
		}
		// eor1 : a ^ b
		//这是 Brian Kernighan算法,可以提取出二进制里最右侧的1
        //为什么要提取出a^b最右侧的1,这样是因为,在a^b最右侧1的位置,a和b一定不相同,他们中一定是其中一个是0,另一个是1
		int rightOne = eor1 & (-eor1);
		int eor2 = 0;
		for (int num : nums) {
			if ((num & rightOne) == 0) {    //说明这个数num在a^b的最右侧为1的那个位置,不是1,而是0
				eor2 ^= num;        //得到的eor2,在a^b的最右侧为1的那个位置,一定是0,这时eor2一定是a和b中其中一个
			}
		}
		return new int[] { eor2, eor1 ^ eor2 };    //如果eor2是a,那么eor1^eor2一定是b
	}

}

异或运算应用6:数组中只有1种数出现次数少于m次,其他数都出现了m次,返回出现次数小于m次的那种数

137. 只出现一次的数字 II - 力扣(LeetCode)

public class Code06_OneKindNumberLessMtimes {

	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) {
			for (int i = 0; i < 32; i++) {
				cnts[i] += (num >> i) & 1;
			}
		}
		int ans = 0;
		for (int i = 0; i < 32; i++) {
			if (cnts[i] % m != 0) {
				ans |= 1 << i;
			}
		}
		return ans;
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值