位运算技巧第二篇

前面整理了位运算技巧顺便说了一些题目,还有一些题目没有整理,今天就对我知道的一些在进行整理。

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(小羊)

参考资料:
《程序员代码面试指南…》-- 左程云

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值