1.注意事项
使用异或运算交换数据很容易忽视的一点是:
要交换的两个变量的值可以相同,但是交换的数据不能是同一块内存区域
可以用下面的代码举个例子,有三个变量,a=2,b=2,arr={1,1}。
使用异或运算交换a和b没有出现问题,但是交换arr[0]和arr[0]却出现了问题 。如下图所示
public class Test {
public static void main(String[] args) {
int a=2;
int b=2;
System.out.println("原始数据:a="+a+", b="+b);
a=a^b;
b=a^b;
a=a^b;
System.out.println("a和b交换数据:a="+a+", b="+b);
System.out.println("----------------------------");
int[] arr={1,1};
System.out.println("原始数据:arr[0]="+arr[0]);
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]交换数据后:arr[0]="+arr[0]);
}
}
运行结果如下图:
为什么会这样呢?
-
原因很简单,分析一下代码可以看出来:如果使用异或运算交换的是同一块内存区域,
在执行arr[0]= arr[0] ^ arr[0]
;时,arr[0]这一块内存区域的值已经变成了0;
后面再执行arr[0]=arr[0]^arr[0]
时,arr[0]的值也只能一直是0; -
也可以从另外一个 角度思考:使用异或交换时
a=a^b;
b=a^b;
a=a^b;
- 如果a和b指向的不是同一块内存区域,更改a的值的时候,b的值不会受到任何影响,也就是说执行完第一行代码a=a^b之后,b的值还没有任何变化
- 但是如果a和b指向的是同一块内存区域的话,更改a的值的时候,b的值也会受到影响,这时候就出现了问题。执行完第一行代码a=a^b时,a=0,因为a和b指向的是同一块内存区域,这时候,b的值也变为了0,后面无论怎么进行异或运算,a和b永远是0;
所以,使用异或运算交换数据的时候,一定要注意,要交换的两个数据值可以相同,但是不能指向同一块内存区域。如果指向同一块 内存区域,这块 内存区域的值就变为了0
另外异或运算满足结合律和交换律,所以一组数据异或运算时,无论数据的顺序是什么样的,无论怎么结合,结果都是一样的
2.适用题型
- 不开辟额外的空间交换两个数字(解法:使用上述代码进行异或运算交换数据)
- 一个数组中只有一种数字出现了奇数次,其余的数字都出现了偶数次,请你找出这个出现奇数次的数据(解法:只需要将数组中所有的数据全部进行异或运算,然后返回结果即可)
- 一个数组 中 有两个数字出现了奇数次,其余数字都出现了偶数次
解法如下 :
- 将数组中 的所有数字进行异或,结果放在变量 a里。假设那两个出现奇数次的数字是num1和num2,这时a其实就等于num1^num2。
- 取出a二进制形式的最右侧(任意一位都行)的一,假设是第k位为1,说明那两个出现奇数次的数字在该位一个为0,一个为1;
- 将所有的数字分为两类,一类是第k位为1(这一类中的数字由num1和出现偶数次的其他数字),一类是第k位为0(这一类中的数字由num2和出现偶数次的其他数字).
- 将a与其中一类异或,得到第一个出现奇数次的数字num1
- 将a与num1异或,得到第二个出现奇数次的数字num2
代码如下:
class Solution {
public int[] singleNumber(int[] nums) {
int temp=nums[0];
for(int i=nums.length-1;i>0;i--){
temp=(temp^nums[i]);
} //假设两个出现奇数次的数字是num1和num2,那么temp=num1^num2;
//取出temp最右侧的一个1,假设是在第k位
int test=temp&((~temp)+1);
int num1=0;
//num1和num2的第k位一个为1,一个为0,
for(int i=nums.length-1;i>=0;i--){
if((test&nums[i])==test){ //将第k位为1的进行异或运算,最后得到的就是num1
num1^=nums[i];
}
}
temp=temp^num1; //temp = num1^num2^num1 = num2;
return new int[]{temp,num1};
}
}
作者:nan-ke-yi-meng-8r
链接:https://leetcode.cn/problems/single-number-iii/solution/by-nan-ke-yi-meng-8r-h6qn/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.分享几个其他位运算的题目
-
给你任意一个数字a,请你返回一个数字b。要求:b的二进制形式只有一位为1,其他位全为0,为1的那一位是数字a二进制形式中最右侧的1所在的位置。
例如,给你一个数字12=b’1100,返回的结果为4=0100;
解法:b=a&(-a),返回b即可 -
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
- 解法:
class Solution {
public int singleNumber(int[] nums) {
/**
数组bit[i]表示数组中每个数的二进制形式的第i位是1的次数,
假设第k位不是3的整数倍,那么说明返回结果(只出现一次的数字)的第k位为1
如果第k位是三的倍数,说明返回结果的第k位为0
*/
int[] bit=new int[32];
for(int num:nums){
for(int i=0;i<32;i++){
bit[i]+=((num>>i)&1); //如果num的第i位为1,bit【i】加1
}
}
int ans=0;
for(int i=0;i<32;i++){
if(bit[i]%3!=0){ //第i位的数字如果不是3的倍数,说明返回结果第i位为1
ans= (ans|(1<<i)); //将返回结果的第i位置一
}
}
return ans;
}
}