算法题里面,涉及到按位运算,有2个经典位运算:
- x&(-x) : 保留二进制下最后出现1的位置的数字,其余位置置0;
- x&(x-1) : 清除二进制下最后出现1的位置的数字,其余位置保持不变;
x可以将二进制数写作(A) 1 (B)的形式,其中A表示一系列01串,1表示从右向左出现的第一个数字1,B表示空串(x是奇数)或者全0串(x是偶数)。那么,
x是奇数时,x = (A) 1
x是偶数时,x = (A) 1 (0..0)
对于x&(-x)
推演
1)先求-x,
-x代表x的相反数,在计算机中存储的是补码,也就是说,-x实际存储的是x反码 + 1
x是奇数时,-x = (~A) 0 + 1 = (~A) 1
x是偶数时,-x = (~A) 0 (1..1) + 1 = (~A) 1 (0..0)
2)再求x&(-x),
x是奇数时,x&(-x) = (0..0) 1
x是偶数时,x&(-x) = (0..0) 1 (0..0)
其中,1右边的0位数保持不变。也就是说x&(-x) 会保留二进制最右边出现的1位置对于数字,其余位置置0。
应用
求非负整数x能被2的n次幂整除的n最大值。
考虑:
如果x为奇数,∵x无法被2整除,只能被1整除 ∴n=0
如果x为偶数,∵整除一个2的n次幂数,相当于右移n位
∴要求n的最大值,等价于求x最多能右移位数保持位1个数不变的移动数量 <=> 求x最右边连续0的个数
如8 = 1000b,右移3位1个数不变,右移4位1的个数减少,而8最大能被2^3整除。
24 = 1 1000b,右移3位1个数不变,右移4位1个数减少,而24最大能被2^3整除。
故只需要保留最右边的1即可,因为只需要计算最右边连续0的个数。
#include <stdio.h>
int maxPowerOfTwo(int d) {
d = d&(-d);
int num = 0;
while(d) {
num ++;
d = d >> 1;
}
return num-1;
}
int main(void) {
int n = maxPowerOfTwo(24);
printf("%d
", n);
return 0;
}
对于x&(x-1)
推演
1)先求x-1,
x是奇数时,x-1 = (A) 0
x是偶数时,x-1 = (A) 0 (1..1)
2)再求x&(x-1)
x是奇数时,x&(x-1) = (A) 0
x是偶数时,x&(x-1) = (A) 0 (0..0)
其中,原来1(现在的0)右边的位数保持不变。
应用
示例1:求32位整型数d对应二进制数包含的1的个数。
#include <stdio.h>
// 求二进制数d包含1的位数
int countBitOne(int d) {
int num = 0;
while(d)
{
num++;
d = d & (d-1);
}
return num;
}
int main(void) {
int d = 0b101000;
int nums = countBitOne(d);
printf("%d
", nums);
return 0;
}
示例2:判断一个数是否为2的n次方。
思路:
2的n次方是形如1,2,4,8,16,32,...这样的数
1 -- 1b
2 -- 10b
4 -- 100b
8 -- 1000b
16 - 10000b
32 - 100000b
...
这样数的特点是二进制数据只包含1个位1,如果能将唯一的1清除,则必为0。考虑使用x&(x-1)将二进制数最后一位1清除。
#include <stdio.h>
#include <stdint.h>
bool isTwoPower(int d) {
if (d&(d-1) == 0) {
return true;
}
return false;
}
推论
- x - x&(-x) = x&(x-1)
左边式子表示在二进制数x基础上,清除最右边为1的数,与右边式子含义一样。