前面整理了位运算技巧顺便说了一些题目,还有一些题目没有整理,今天就对我知道的一些在进行整理。
1、不用任何比较判断两个数的大小
给定两个32位整数 a 和 b,要求不使用任何比较判断,返回 a 和 b 中较大的数。
a 和 b 的较大值,如果 a - b > 0 则 a 大,否则 b 大。而结果的正负值我们通过最高位的状态是能知道的。我们假设最大值 x = am + bn,如果a-b>0,我们令m为1,n为0,则计算结果为 x = a。同理如果 a - b < 0,我们令m为0,n为1,则计算结果为x = b。
具体的实现如下所示:
int flip ( int n )
{
return n ^ 1;
}
int sign ( int n )
{
return flip( (n >> 31) & 1);
}
int getMax( int a, int b )
{
int c = a - b;
int sca = sign(c);
int scb = flip( sca );
return a*sca + b*scb;
}
flip函数的功能,如果n为1,返回0,如果n为0,返回1
sign函数的功能是返回整数n的符号,正数和0返回1,负数则返回0
getMax函数就是求结果了。
虽然上面的看起来没有问题,但是会有溢出问题。如 a = -1 *
2
31
2^{31}
231,b =
2
3
2^3
23,则两者相减会导致溢出。
对于溢出只会发生在,两者符号不一致的情况下,所以我们可以先判断两者的正负号:
如果 a 为0或正数,b为负数,则返回a
如果a为负数,b为0或正数,则返回b
如果两者符号相同,则跟前面的一样,所以我们修改代码如下:
getMax ( int a, int b )
{
int c = a - b;
int sa = sign(a);
int sb = sign(b);
int sc = sign(c);
int difsab = sa ^ sb;
int samesab = flip(difsab);
int returna = difsab * sa + samesab * sb;
int returnb = flip(returna);
return a * returna + b * returnb;
}
2、二进制中1的个数
前面介绍了使用 n & ( n -1 ) 求二进制中 1 个数的方法,下面介绍其它的方法:
我们使用 n & (~n+1)可以得到 n 中最右侧的 1。然后 让 n 等于 n 减去这个结果,相当于消除了最右侧的1,循环操作,得到最后的结果。具体代码如下:
int func ( int n)
{
int res = 0;
while( n != 0 )
{
n -= n & ( ~n + 1 );
res++;
}
return res;
}
3、缺失的数字,进阶版
前面整理一个整数数组,只有一个整数出现了奇数次,其它都出现了偶数次,找出这个数。现在进阶一下。
如果这个数组中有两个数出现了奇数次,求这两个数。
假设这两个数为 a 和 b ,我们还是把所有的数据求异或,得到的结果 e = a ^ b。最终的结果一定不为0的,因为是0说明 a == b。那么我们会在e上找到一个不等于 0 的 bit 位,假设是第 K 位不等于0。e 的第 K位不等于0,说明 a 或者 b 的第K位不等于 0。我们在设置一个变量 hasone,只将变量的第 K 位设置为1,其它位为 0。在遍历一遍数组,这次遍历时,只与第K位为1的数字进行 ^ 操作,其他数忽略。则第二遍遍历的数就是要求的其中一个结果,与之前的结果异或,求得另外一个结果。具体的代码如下:
int func( int* arr, int length )
{
int lostnum1 = 0, lostnum2 = 0;
for(int i = 0; i < length; i++)
{
lostnum ^= arr[length];
}
//取得某一位是1的数字
int rightone = lostnum1 & (~lostnum1 + 1 );
for( int i = 0; i < length; i++)
{
if( (arr[length] & rightone) != 0 )
{
lostnum2 ^= arr[length];
}
}
cout << "两个数分别是:" << lostnum2 << " " << (lostnum1 ^ lostnum2);
}
4、在其他数都出现K次的数组中找到只出现一次的数
给定一个整数数组arr和一个大于 1 的整数 k 。已知arr中只有 1 个数出现了 1 次,其它的数都出现了 k 次,请返回只出现 1 次的数。要求时间复杂度为O(N),额外空间复杂度为O(1)。
首先来看一个例子,有两个7进制的数无进位相加,比如:
七进制数a: 6 4 3 2 6 0 1
七进制数b: 3 4 5 0 1 1 1
相加结果c: 2 1 1 2 0 1 2
可以看出,两个七进制的数 a 和 b,在 i 位上无进位相加的结果就是 (a(i)+b(i))%7。同理,k进制的两个数 c 和 d,在 i 位上无进位相加的结果就是 (c(i)+d(i))%k。那么,如果 k个相同的 k 进制数进行无进位相加,相加的结果一定是每一位上都是 0 的 k 进制数。
理解了上述过程之后,解这道题就变得容易了。首先设置一个变量 res,它是一个32位的 k 进制数,且每个位置上都是 0 。然后遍历arr,把每个数都转换成 k 进制数,然后与 res进行无进位相加。遍历结束时,把32位的 k 进制数 res转换成十进制整数,就是我们想要的结果。因为还有一种比较简单的方法,这里我就不写这种方法的代码了。
还有一种比较简单的方法是,对于任意个数重复 k 次,则它二进制位不进位相加一定是k的倍数。我们可以不数组中每一个数的位数相加,然后每一位除 k 取余,这样组成的数就是要的结果。具体代码如下:
int FindNumber(int a[], int n)
{
int bits[32];
int i, j;
//累加数组中所有数字的二进制位
memset(bits, 0, 32 * sizeof(int));
for (i = 0; i < n; i++)
for (j = 0; j < 32; j++)
bits[j] += ((a[i] >> j) & 1);
//如果某位上的结果不能被整除,则肯定目标数字在这一位上为
int result = 0;
for (j = 0; j < 32; j++)
if (bits[j] % 3 != 0)
result += (1 << j);
return result;
}
5、只用位运算不用算术运算实现整数的加减乘除法
不用算术运算法实现整数的加法,前一章节已经整理了,减法就相当于加(-n)。这里我们主要看实现乘法和除法。
用位运算实现乘法运算。我们需要知道ab的结果可以写成 a* 2 0 2^0 20*b0+a* 2 1 2^1 21*b1+…+a* 2 i 2^i 2i*bi+…+a* 2 31 2^{31} 231**b31。其中,bi为0或1代表整数b的二进制数表达式中第i位的值。实现代码如下:
int multi(int a, int b)
{
int res = 0;
while ( b != 0 )
{
if( (b & 1) != 0 )
{
res = add (res, a);
}
a <<= 1;
b >>= 1;
}
return res;
}
用位运算实现除法运算,其实就是乘法的逆运算。我还没看明白,等整明白再来补上。
好了今天就整理到这了。
感谢大家,我是假装很努力的YoungYangD(小羊)。
参考资料:
《程序员代码面试指南…》-- 左程云