异或(^)在数的交换中和找出现次数不同的数的应用(十分实用)

异或的定义和特性

1)

运算规则:0^0=0;  0^1=1;  1^0=1;   1^1=0;即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。

2)满足交换律和结合律,a^b=b^a;   a^b^c=a^(b^c);

举个交换的例子

  我们定义了俩个变量:int a=15;int b=10;

一般交换的方式是申请一个中间变量temp,temp=a;a=b,b=temp;

 但如果使用异或的话,可以不需要辅存空间

a=a^b;

b=a^b;   //b=a^b^b=a;

a=a^b; //a=a^b^a=(a^a)^b=b;

 成功实现交换。

 接下来用俩个问题来介绍异或的下一个用法

问题一:在一个数组中,一种数出现了奇数次,其他数都出现了偶数次,请找出那个出现了奇数次的数。
问题二:在一个数组中,俩种数出现了奇数次,其他数都出现了偶数次,请找出这俩个出现了奇数次的数。

俩个问题都要求时间复杂度o(n),空间复杂度o(1)

问题一的解法:使用值为0的整形数eor去和数组的每一位进行异或运算,最后得到的就是唯一一个出现次数为奇数次的数。(原理就是异或具有交换律和结合律,出现偶数次的数可以和自身进行异或,然后值为0,0和任何数异或都是那个数本身)

public class numberOne {

	public static void main(String[] args) {
		//问题一的异或解法
		int eor=0;
		//我们准备一个数组,
	  int []arr=new int[] {11,12,13,11,12};
	    for(int i=0;i<arr.length;i++) {
	    	eor^=arr[i];
	    }
	    System.out.println(eor);
	    
	    eor=0;
	    int []arr1=new int[] {11,12,13,11,12,13,15,17,17};
	    for(int i=0;i<arr1.length;i++) {
	    	eor^=arr1[i];
	    }
	    System.out.println(eor);
	}
}

 下面是运行得到的结果,确实交换过来了。

 

 问题二的解法:

大部分解释都在代码的注释部分,这里介绍一下与运算(&)按位与运算符 比如a&b;表示把a和b进行二进制的按位与运算 比如8&10,其中8的二进制是0000 1000,而10的二进制是0000 1010,因此       0000 1000(十进制8)     & 0000 1010(10进制10) 结果为0000 1000(就是10进制的8) 因此8&10的结果为8 与的计算规则是,如果两个数都都为真(或为1),其结果为真,如果两位数中有一位为假(或为0)者结果为假。

//问题二,在一个数组中,俩种数出现了奇数次,其他数都出现了偶数次,请找出这俩个出现了奇数次的数。俩个问题都要求时间复杂度o(n),空间复杂度o(1)
public class numberTwo {

	public static void main(String[] args) {
		
	int []arr=new int [] {1,2,3,2,3,5,8,5};
     questionTwo(arr);
     
	}
	public static void  questionTwo(int []arr) {
		int eor=0,onlyOne=0;
		for(int i=0;i<arr.length;i++) {
			eor^=arr[i];
		}
		//eor=a^b;//a和b分别是出现奇数次的数
		//eor!=0
		//eor必然有一个位置上是1(这里相对的是二进制的表示形式,例如二进制下的0101(5),0100(4),5!=4;5^4=0001=1(!=0);5^5=0000=0)
		//只有每一位都相同,即俩个数相同,那么异或下来才是0,那么两个数不相同,那么在二进制表示下至少有一位上这两个数不一样,一个数为1,一个数为0)
		//这里介绍用二进制表示数是为了后面好分类,例如可以分成最右一位上是1的数和最右一位上为0的数。
		int rightOne=eor&(~eor+1);//提取出最右侧的1;这里的“~”是取反操作,就是二进制数的每一位取反,0变1,1变0;
		//原理在这,例如这个数为011010,我们先取反,变成100101,然后再加个1,不就变成了100110
		//然后我们再使用&运算,这样得到的数不就是000010吗,取出了最右侧的那个1,这下我们就可以根据这个来分类了
		
		for(int i=0;i<arr.length;i++) {
			if((arr[i]&rightOne)==0) {
				onlyOne^=arr[i];//这一步是提取出符合我们确定的标志位的数,例如最右一位上为1的数,然后进行异或运算,得到a和b中那个在此标志位上为1的数
			}
		}
	   System.out.println(onlyOne+" "+(eor^onlyOne));//eor=a^b,a和b中的一个数赋值给了onlyOne,假如这个数为a,那么eor^onlyOne=a^b^a=b;这样就求得了俩个数
		
	}
}

运行结果如下图:

 学算法的时候看到这俩个例题(好像还是某公司的面试题),解法让我眼前一亮,然后加上自己的理解,写了这篇分享,如果解释不当之处,还望大家指正。

猜你喜欢

转载自blog.csdn.net/qq_50977450/article/details/121078129
今日推荐