如何通过位运算统计数字中1的数量(leetcode338)
题目:
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
解题思路:
这个题目的重点在于计算数字
n
n
n中
1
1
1的个数。
计算一个数字中1的个数的常用方法是位运算。第一种方法是将数字n右移,与1与求得
n
n
n的个数:
int count = 0;
while (n != 0) {
count += n & 0x1;
n >>= 1;
}
这种方法需要注意的是java
中逻辑右移与算数右移的区别。java中逻辑右移是在
n
n
n左边补0,而算数右移当
n
n
n为正数时补0负数时补1。对于负数来说,上面的代码会造成无限循环,解决方法之一就是将右移运算符替换成java
的逻辑右移运算符>>>
。第二种解法是不右移
n
n
n,而是左移1:
int count = 0;
int mask = 1;
for (int i = 0; i < 32; i++) {
if (n & mask > 0) count++;
mask <<= 1;
}
第三种方法比较巧妙。我们注意到 n & ( n − 1 ) n \& (n - 1) n&(n−1)的结果将 n n n最右边的1变成了0,因此我们可以用这种方法统计 n n n中1的数量:
int count = 0;
while (n > 0) {
n = n & (n - 1);
count++;
}
发现了规律 n & ( n − 1 ) n \& (n - 1) n&(n−1)的结果将 n n n最右边的1变成了0之后,我们就可以推得递推关系式:
nums[i] = nums[i & (i - 1)] + 1
这样的话就可以在 O ( n ) O(n) O(n)的时间复杂度里面求得1~n中所有数1的个数:
class Solution {
public int[] countBits(int num) {
// 处理边界条件
if (num == 0) return new int[]{0};
int[] ans = new int[num + 1];
ans[0] = 0;
ans[1] = 1;
for (int i = 2; i <= num; i++) {
ans[i] = ans[i & (i - 1)] + 1;
}
return ans;
}
}