异或运算可以理解为无进位相加
那么就不难理解,异或运算满足交换律跟结合律,即结果与异或顺序没有关系
重要结论 N^N=0
例题
假设白为1,黑为0,那么袋子里相当于a个0,b个1;
设c=a^b,把这个结果放入袋子里,再重复操作;
那么最后剩下的那个数,就是袋子中全部数字的异或结果
1.交换两数
利用性质 N^N=0
int a = 10, b = 20;
a = a ^ b;
b = a ^ b; //b=a^b^b=a
a = a ^ b; //a=a^b^a=b
printf("%d %d", a, b);
使用需要注意如果进行数组内容交换,下标不能相同
2.不使用判断语句返回两个数的最大值
int sign(int n) //取出二进制符号位
{
return (n >> 31) & 1 ^ 1 ; //非负1,负数0
}
int flip(int n)
{
return n ^ 1; //1变0,0变1
}
int getmax1(int a, int b) //a-b的差值c有溢出风险
{
int c = a - b;
//c非负,returnA为1 ,returnB为0
//c负数, 则为0, 为1
//即只有一个1和一个0
int returnA = sign(c);
int returnB = flip(returnA);
return returnA * a + returnB * b;
}
int getmax2(int a, int b) //没有风险的实现
{
int c = a - b; //c仍可能溢出
//a,b,c的符号与1异或值 非负1,负数0
int sa = sign(a);
int sb = sign(b);
int sc = sign(c);
int diffAB = sa ^ sb; //判断a和b符号是否一样,同号为0.异号1
int sameAB = flip(diffAB); //与以上结果正好互斥!
//一.a b符号不同且a非负 二.a b符号相同且c非负(a>b) 满足两个返回a
int returnA = diffAB * sa + sameAB * sc;
int returnB = flip(returnA);
return returnA * a + returnB * b;
}
3.找到缺失的数字
这个简单一些
int missingNumber(int* nums, int numsSize)
{
int x=0;
for(int i=0;i<numsSize;i++)
{
x^=nums[i];
}
for(int j=0;j<numsSize+1;j++)
{
x^=j;
}
return x;
}
4.数组中1种数出现了奇数次,其他的数都出现了偶数次,返回出现了奇数次的数
这是力扣136题,(不需要额外空间的方法,就往位运算上想–来自评论区)
int singleNumber(int* nums, int numsSize)
{
int eor=0;
for(int i=0;i<numsSize;i++)
{
eor^=nums[i];
}
return eor;
}
Brian Kernighan算法 - 提取出二进制状态中最右侧的1
一个数取反+1就是它的相反数 (~n)+1
再把原数字与上它 n&((~n)+1)
就是 n&(-n)
5.数组中有2种数出现了奇数次,其他的数都出现了偶数次,返回这2种出现了奇数次的数
设这两数为a,b
eor1=a^b
令rightOne=eor1&(-eor1)
那么提取出eor1二进制状态中最右侧的1后,只有两种情况,
那就是在那一位上为1或为0的数
第一种情况为一组数异或rightOne后为0,把他赋给eor2,假设eor2是a
第二种情况只需eor1^eor2=a ^ b ^ a=a
int* singleNumber(int* nums, int numsSize, int* returnSize)
{
long eor1=0; //用long是因为negation of -2147483648 cannot be represented in type 'int'
for(int i=0;i<numsSize;i++)
{
eor1^=nums[i];
}
//eor1=a^b
int eor2=0;
int rightOne=eor1&(-eor1);
for(int i=0;i<numsSize;i++)
{
if((rightOne & nums[i])==0)
{
eor2^=nums[i];
}
}
int*ans=(int*)malloc(8);
ans[0]=eor2;
ans[1]=eor1^eor2;
*returnSize=2;
return ans;
}
6.数组中只有1种数出现次数少于m次,其他数都出现了m次,返回出现次数小于m次的那种数
力扣137题,题中m=3
在二进制上,一个数出现m次,它二进制形式里的每个位上的1和0都会出现m次
我们只考虑1,对每个位上的1累加,对于每个累加和
%m后如果是0,那么那个出现次数少于m次的数的二进制形式在这个位上是0,反之为1
最后,要实现二进制补1,只需进行或运算,且需要注意使用1u而不是1
int singleNumber(int* nums, int numsSize) {
//cnts[i] : i位上有多少个1
int cnts[32]={0};
for(int i=0;i<32;i++)
{
for(int j=0;j<numsSize;j++)
{
cnts[i]+=(nums[j]>>i)&1; //向右移动i位
}
}
unsigned int ans=0;
for(int i=0;i<32;i++)
{
if((cnts[i])%3 !=0)
{
ans|=(1u<<i); //必须是1u而不能是1,因为1u可以处理负数
}
}
return ans;
}
另外附上力扣189轮转数组:给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
原数组中 在k=3的位置处,4和5相连
轮转后,7和1相连
不难得出方法:将数组左边和右边分别旋转,就能使7和1相连
最后对数组整体旋转从而纠正顺序
void reverse(int*nums,int left,int right)
{
while(left<right)
{
int tmp=nums[left];
nums[left]=nums[right];
nums[right]=tmp;
right--;left++;
}
}
void rotate(int* nums, int numsSize, int k)
{
if(k)
{
k=k%numsSize;
reverse(nums,0,numsSize-k-1);
reverse(nums,numsSize-k,numsSize-1);
reverse(nums,0,numsSize-1);
}
}