一、位1的个数
我们要判断一个数的二进制中有多少个1,最简单的方式就是在循环中与1进行与运算之后判断是否为1,然后1<<1,这样子就可以完成目标了。但是不够不够简练,首先我们要知道一个规律。
n:0001 0000 1001 0010 0100 n-1:0001 0000 1001 0010 0011
n&(n-1):0001 0000 1001 0010 0000
所以每一次n和(n-1)进行与运算都可以将一个1消掉。所以我们可以这么做
public int hammingweight(int n){
int ones = 0;
while(n!=0){
n &= (n-1);
ones++;
}
return ones;
}
二、颠倒无符号整数
首先这里说是无符号位,那不必考虑正负的问题,最高位的1也不表示符号位,这就省掉很多麻烦。我们注意到对于 n 的二进制表示的从低到高第i位,在颠倒之后变成第 31-i 位 0i32),所以可以从低到高遍历 n 的二进制表示的每一位,将其放到其在颠倒之后的位置,最后相加即可。看个例子,为了方便我们使用比较短的16位演示:
原始数据: 0000 1001 0010 0100
reversed:0*** **** **** ****
原始数据右移一位:0000 0100 1001 0010
所以我们的代码可以是
public int reverseBits(int n){
int reversed = 0, power = 31;
while(n!=0){
reversed += (n&1)<<power;
n >>>= 1;
power--;
}
return reversed;
}
三、位运算实现加法
输入: a= 1(01),b= 2(10)
输出:3(11)
二进制运算有四种情况
0+0=0
0+1=1
1+0=1
1+1=0(进位1)
两个位加的时候,我们无非就考虑两个问题: 进位部分是什么,不进位部分是什么。从上面的结果可以看到,对于a和b两个数不进位部分的情况是: 相同为0,不同为1,这不就是a⊕b吗?而对于进位,我们发现只有a和b都是1的时候才会进位,而且进位只能是1,这不就是a&b=1吗? 然后位数由1位变成了两位,也就是上面的[4]的样子,那怎么将1向前挪一下呢? 手动移位一下就好了,也就是(a &b) << 1。所以我们得到两条结论:
不进位部分: 用a⊕b计算就可以了。
是否进位,以及进位值使用(a & b) << 1计算就可以了
public int getSum(int a,int b){
while(b!=0){
//进位的结果
int sign = (a&b)<<1;
a = a ^ b;
b = sign;
}
return a;
}
四、递归乘法
LeetCode里面试08.05,递归乘法。写一个递归函数,不使用* 运算符,实现两个正整数的相乘。可以使用加号、减号、位移,但要一些。
如果不让用*来计算,一种是将一个作为循环的参数,对另一个进行累加,但是这样效率太低,所以我们还是要考虑位运算。
首先,求得A和B的最小值和最大值,对其中的最小值当做乘数(为什么选最小值,因为选最小值当乘数,可以算的少),将其拆分成2的幂的和,即min = a 0* 20 + a 1* 21 + ... + a * 2i +.其中a i取或者1。其实就是用二进制的视角去看待min,比如12用二进制表示就是1100,即1000+0100。例如:
13 *12 = 13 *(8 + 4) = 13* 8 + 13* 4 =(13 << 3) + (13 << 2);上面仍然需要左移5次,存在重复计算,可以进一步简化:假设我们需要的结果是ans,
定义临时变量: tmp=13<<2 =52计算之后,可以先让ans=52然后tmp继续左移一次tmp=52<<1=104,此时再让ans=ans+tmp这样只要执行三次移位和一次加法,实现代码:
public static int multiply(int A, int B) {
int max = A > B ? A : B;
int min = A < B ? A : B;
int ans = 0;
for (int i = 0; min != 0; i++) {
if ((min & 1) == 1) {
ans += max;
}
min >>= 1;
max <<= 1;
}
return ans;
}