位运算相关例题
位运算:&、|、^、~、<<、>>
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位。对无符号数,高位补0;有符号数,一般都是补符号位(算术右移) |
备注:对于有符号位的右移,有的编译器,会直接补0(逻辑右移)
利用“异或”处理数组的相关算法
异或运算
异或是一种基于二进制的位运算,用符号XOR或者 ^ 表示。运算时对运算符两侧的数转化为二进制形式,同值取0,异值取1。
异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位。
- 性质
1、交换律
2、结合律(即(a^b)^c == a^(b^c))
3、对于任何数x,都有x^x=0,x^0=x
4、自反性A ^ B ^ B = A^ 0 = A
寻找数组中丢失的数
- 缺失数字
给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
https://leetcode-cn.com/problems/missing-number/solution/que-shi-shu-zi-by-leetcode/
方法一:排序
方法二:哈希表
方法三:位运算:由于异或运算(XOR)满足结合律,并且对一个数进行两次完全相同的异或运算会得到原来的数,因此我们可以通过异或运算找到缺失的数字。
方法四:数学,等差数列求和,注意防止数据溢出
找出数组中两个只出现一次的数字
-
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)
-
思路:
我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。
由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。
现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0。
现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。
到此为止,所有的问题我们都已经解决。
代码示例
void FindNumsAppearOnce(int data[], int length, int &num1, int &num2)
{
if (length < 2)
return;
// get num1 ^ num2
int resultExclusiveOR = 0;
for (int i = 0; i < length; ++ i)
resultExclusiveOR ^= data[i];
// get index of the first bit, which is 1 in resultExclusiveOR
unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
num1 = num2 = 0;
for (int j = 0; j < length; ++ j)
{
// divide the numbers in data into two groups,
// the indexOf1 bit of numbers in the first group is 1,
// while in the second group is 0
if(IsBit1(data[j], indexOf1))
num1 ^= data[j];
else
num2 ^= data[j];
}
}
unsigned int FindFirstBitIs1(int num)
{
int indexBit = 0;
while (((num & 1) == 0) && (indexBit < 32))
{
num = num >> 1;
++ indexBit;
}
return indexBit;
}
bool IsBit1(int num, unsigned int indexBit)
{
num = num >> indexBit;
return (num & 1);
}
有N+2个数,N个数出现了偶数次,2个数出现了奇数次(这两个数不相等)
------------------------------------------------------------------------------------------------腾讯笔试题
问用O(1)的空间复杂度,找出这两个数,不需要知道具体位置,只需要知道这两个值
思路:同上
第一步:数组中每个数异或一次,得到2个奇数次的数的异或结果X(偶数次的数异或都为0)。
第二步:两个不同数异或得到的X,一定不为0;我们找出X的二进制数中第一个为1的位置,记为N(这两个不同数的二进制在第N位是不同的),然后根据第N位是否为1,将所有的数分为2组,这时,两个出现奇数次的数一定是分别在其中一组。
第三步:对每组的中每个数异或一次,最后得到的两个数就是结果
n&(n-1)的作用
n&(n-1):消除数字 n 的⼆进制表⽰中的最后⼀个1
例题
1.判断⼀个数是不是 2 的指数
⼀个数如果是 2 的指数, 那么它的⼆进制表⽰⼀定只含有⼀个 1:
bool isPowerOfTwo(int n){
if(n<=0) return false;
return (n&(n-1))==0;
}
- 统计一个二进制数n中1的个数(汉明权重)
关于移位
- 计算器存储的都是补码,正数的补码是其原码,负数的补码是由原码变换而来:符号位不变,数值位取反,加1。
- 对于正数的移位:左移后在右边补0,若一直左移,最终是0;右移后再左边补0,若一直右移,最终是0
- 对于负数的移位:左移和正数一样在右边补0,一个负数再左移过程中有正有负,不用处理符号位,若一直左移,最终是0;负数右移则需要保持为负数,所以右移过程中需要在左边补1,若一直右移,最终会为-1 (二进制全1,就是-1的补码)
利用上面的n&(n-1)消除最低位的1
int NumberOf1(int n){
int count =0;
while(n){
n=n&(n-1);
count++;
}
return count;
}
注意这题不要通过右移,记录最低位为1的个数,因为负数右移最终会为-1(二进制全1)
可以用一个flag=1;依次与数n相与,判断是否为0,是,count++;然后flag<<1,再循环上述操作,直到flag最终左移为0。
int NumberOf1(int n){
int count=0;
unsigned int flag=1;
while(flag){
if(flag&n)
count++;
flag=flag<<1;
}
return count;
}
判断奇偶数
- if(n&1==0):偶数
- if(n&1==1):奇数
判断两个数是否异号
计算机存储的都是补码,
异号的数异或的结果,符号位是1,是负数<0;
同号的数异或的结果,符号位是0,是正数>0;
//判断a,b是否异号
bool func(int a,int b)
{
return ((a^b)<0);
}