异或中的骚操作
今日刷LeetCode发现了只出现一次的数字的系列题,并且深深领会到异或中的骚操作,接下来介绍一些说完让你眼前一亮的操作
首先异或的基础介绍
什么是异或操作,简单来说就是相同为0,不同为1,更好记忆的方法是不进位+运算,举个简单的栗子:6^7=1
所以不难得到:0^N=N
,N^N=0
异或操作是满足结合律和交换律的,即多个数即异或结果和顺序无关
不需要额外空间的互换值
maybe交换两个数的值是我们最开始接触过的代码,很简单,用一个额外变量
int a = 1,b = 2,temp;
temp = a;
a = b;
b = temp;
但如果使用异或操作可以不使用额外的空间
int a = 1,b = 2;
a = a^b;
b = a^b;
a = a^b;
//输入a,b你会发现a,b交换了数值
怎么实现的呢?
但请注意:
对于使用两块不同内存空间的数值,这么操作没有问题,假如两个数共享的是一个内存区域,譬如:
public static void main(String[] args) {
int a = 2,b=2;
a = a^b;
b = a^b;
a = a^b;
System.out.println(a+" "+b);// a = 2,b=2
int[] arr = {1,2,3};
arr[0] = arr[0]^arr[0];
arr[0] = arr[0]^arr[0];
arr[0] = arr[0]^arr[0];
System.out.println(arr[0]);//arr[0] = 0;
}
其实不难理解,交换两个数值最起码需要两块内存空间,如果只有arr[0]一块空间,自己来回异或自己结果肯定为0
虽然这操作不常见,也不常用,但关键时刻装装x,炫炫技,也不是不行
找出只出现一次的数字(I)
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
根据我们前文提到的异或操作的性质,异或满足交换律和结合律,0^N=N
,N^N=0
,所以将所有的数组中所有的数字异或起来,出现次数为偶数次的异或结果为0,只出现一次的数与0异或结果为本身
public int singleNumber(int[] nums) {
//非空数组所以不需要判断数组长度情况
int res = 0;
for(int i = 0;i<nums.length;i++){
res = nums[i]^res;
}
return res;
}
找出二进制位最右侧的“1”
假如一个二进制数00001111010101000,我们怎么找到这个标黄的1呢?
先说结论:N&(~N+1)
public static void main(String[] args) {
int num = 72;//1001000
num = num&(~num+1);//1000
System.out.println(Integer.toBinaryString(num));
}
只出现一次的数组(II)
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
这个进阶题目比一要难上许多,有两个值不相等的元素只出现一次,要用到上面刚刚介绍到的找出最右侧一算法
这个题的思路是什么呢?
一、将所有元素异或,假设两个不同的元素为a,b,那么最后结果一定是res = a^b
二、res一定是不为0的(如果为0,说明两元素相同),那么就表明一定有二进制位为1,我们假设这个二进制位是第6位(随意假设),那么a和b的第六位一定是不同的,也就是一个为0,一个为1
三、我们将第六位按照0或1分为两组,那么a,b一定在不同的组中,其他的两两相同的数一定分到一个组中
四、我们在一个组中将所有的数异或一下,假如我们在a组异或,那么剩下的数就是a,根据第一步得到的a^b,我们令res在异或一下a得到的就是b
五、刚刚的第六位是假设出来的,目的是得到一个二进制位为1的位,所以利用上述方法,可以得到一个最右侧的一位
public static int[] singleNumber(int[] nums) {
int eor = 0;
for(int i = 0;i<nums.length;i++){
eor = eor^nums[i];
}
//eor = a^b
//提取出最右侧1
int rightOne = eor&(~eor+1);
int a = 0;//计算出a
for(int i = 0;i<nums.length;i++){
if((nums[i]&rightOne)!=0){
//说明这个是假设位是0的那一组
a = a^nums[i];
}
}
int b = eor^a;
}