位移的妙用
1、位1的个数
1.1、题目描述
LeetCode191. 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位为 ‘1’ 的个数。
示例1:
输入:00000000000000000000000000001011
输出:3
示例2:
输入:00000000000000000100000000000000
输出:1
1.2、问题分析与解答
首先我们可以根据题目要求直接计算,题目给定的n是32位二进制表示下的一个整数,计算位1的个数的最简单的方法就是遍历n的二进制表示的每一位,判断每一位是否为1,同时进行计数。
那么怎么判断某一位是否为1呢?例如:00001001001000100001100010001001,首先我们注意要识别到最低位的1,可以这么做:
00001001001000100001100010001001
& 00000000000000000000000000000001
= 00000000000000000000000000000001
也就是说将原始数字和1进行与运算就能知道最低位是不是为1了,那其他位置该怎么算呢?
第一种思路是让原始数据不断右移或者是让1不断左移。例如将原始数据右移1位:
00000100100100010000110001000100
& 00000000000000000000000000000001
= 00000000000000000000000000000000
很显然此时可以判断出第二位是0,然后依次将原始数据右移就能判断出每个位置是否为1了。因此是不是1,计算一下(n >> i) & 1就可以了,代码如下:
public int hammingWeight(int n) {
int count = 0;
for (int i = 0; i < 32; i++) {
count += (n >> i) & 1;
}
return count;
}
除了上述方法外,还有一种方法:
按位与运算有一个性质:对于整数n,计算n & (n - 1)的结果为将n的二进制表示的最后一个1变为0。
利用这条性质,令n = n & (n - 1),则n的二进制表示中的1的数量减少一个。重复该操作,知道n的二进制表示中的全部数位变为0,则操作次数即为n的位1的个数,还是看上面的例子:
n: 00000100100100010000110001000100
n-1: 00000100100100010000110001000011
n&(n-1): 00000100100100010000110001000000
可以看到此时n&(n-1)的结果比上一个n少了一个1,如果一直循环执行的话,到最后n等于0时退出循环,这时循环的次数就是原来n中1的个数,代码如下:
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n = n & (n - 1);
count++;
}
return count;
}
2、比特位计数
2.1、问题描述
LeetCode338. 给你一个整数n,对于 0 <= i <= n 中的每一个i,计算其二进制表示中1的个数,返回一个长度为n + 1的数组ans作为答案。
示例:
输入:n=2
输出:[0, 1, 1]
解释:0到n有0,1,2三个数字,每个数字含有1的个数分别为0 1 1个,如下:
0 --> 0
1 --> 1
2 --> 10
2.2、问题分析与解答
本题是上题的扩展,可以直接遍历0到n的每个数,在遍历的过程中对每个数计算其位1的个数。
代码如下:
public int[] countBits(int n) {
int[] bits = new int[n + 1];
for (int i = 0; i <= n; i++) {
bits[i] = countOnes(i);
}
return bits;
}
public int countOnes(int x) {
int ones = 0;
while (x > 0) {
x = x & (x - 1);
ones++;
}
return ones;
}