第一种,简单计算每个整数的二进制形式中1的个数
j = j & (j - 1);
这是经典算法:j & (j-1):每次把 j 的最右边 1 置为 0,循环次数 = 1 的个数
-
每次可以把 j 的最右边一个
1消掉 -
比如:
- j=6 (110) → j-1=5 (101)
- j & (j-1)=100 (4)
-
消掉一个
1后继续循环
我们拿 j=10 举例:
第一步:
-
j=10 (
1010) -
j-1=9 (
1001)
结果:j=8 (1000)
你看,最右边那个 1 被消掉了(原来在第1位的 1)
第二步:
现在 j=8 (1000)
- j-1=7 (
0111)
结果:j=0
计算机做运算时是按二进制位来执行的: & (按位与)本来就是二进制运算符
public class A_CountBits {
public int[] countBits(int num) {//输入:num一个整数
int[] result = new int[num + 1];//初始化结果数组,数字从 `0` 到 `num` 的结果,一共需要 `num+1` 个位置
for (int i = 0; i <= num; ++i) {//遍历所有数字:从 0 到 num
int j = i;//对当前数字 i 建一个临时变量 j
while (j != 0) {//只要 j 还不为 0,就进入循环
result [i]++;//每进入一次循环,就说明 j 至少还有一个 `1`,所以 `result[i]` 的计数加一;累加数字 `i` 的二进制里 `1` 的个数
j = j & (j - 1);//用来把 `j` 的二进制里最右边的一个 `1` 变成 `0`
}
}
return result;
}
public static void main(String[] args) {
A_CountBits obj = new A_CountBits();
int num = 5;
int[] ans = obj.countBits(num);
System.out.print("Count of bits from 0 to " + num + ":");
for (int i = 0; i <= num; ++i) {
System.out.print(ans[i] + " ");
}
System.out.println();
}
}
我们输入的是 num=5
所以计算:
- result[0]
- result[1]
- result[2]
- result[3]
- result[4]
- result[5]
计算每个数的二进制表示中 1 的个数:
| 数字 i | 二进制表示 | 有几个 1 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 10 | 1 |
| 3 | 11 | 2 |
| 4 | 100 | 1 |
| 5 | 101 | 2 |
所以结果数组就是:
ini
复制编辑
result = [0, 1, 1, 2, 1, 2]
第二种,根据“i&(i-1)"计算i的二进制形式中1的个数
更高效的版本(动态规划版本),思路非常经典
-
i & (i - 1):把i的二进制里最右边的一个1消掉 -
result[i & (i - 1)]:就是i 去掉这个最右边的 1 以后,剩下的那个数字中1的个数 -
然后:
+ 1:再加上被你消掉的那个1
举个例子:
比如 i=5 (二进制 101)
- i & (i-1) = 5 & 4 = 4
- result[4] 已经算过,是 1 (因为 100 只有一个 1)
所以:
- result[5] = result[4] + 1 = 2
这样就用之前算好的结果,快速算出新的值:
- 不需要每次都从头统计有多少个 1
- 时间复杂度 O(n)
public class A_CountBits {
public int[] countBits(int num) {
int[] result = new int[num + 1];
for (int i = 1; i <= num; ++i) {
result[i] = result[i & (i - 1)] + 1;
}
return result;
}
public static void main(String[] args) {
A_CountBits obj = new A_CountBits();
int num = 5;
int[] ans = obj.countBits(num);
System.out.print("Count of bits from 0 to " + num + ":");
for (int i = 0; i <= num; ++i) {
System.out.print(ans[i] + " ");
}
System.out.println();
}
}
第三种,根据“i/2”计算i的二进制形式中1的个数
另一个经典也非常巧妙的动态规划写法
i >> 1
- 是把
i的二进制右移一位 - 相当于
i / 2(向下取整)
i & 1
- 取
i的最低位(最后一位)是不是1 - 如果是奇数,最低位是
1→(i & 1)=1 - 如果是偶数,最低位是
0→(i & 1)=0
数字 i 的二进制 1 的个数 =
i 除以 2 后的数字(i >> 1)中 1 的个数 + 最低位是否为 1
举例:
-
i=5 (
101) -
i >> 1 = 2 (
10) -
result[2]=1 (因为 2 有一个
1)
再加上 i 的最低位:101 最右边是 1 → (i & 1)=1
result[5] = result[2] + 1 = 1 + 1 = 2
public class A_CountBits {
public int[] countBits(int num) {
int[] result = new int[num + 1];
for (int i = 0; i <= num; ++i) {
result[i] = result[i >> 1] + (i & 1);
}
return result;
}
public static void main(String[] args) {
A_CountBits obj = new A_CountBits();
int num = 5;
int[] ans = obj.countBits(num);
System.out.print("Count of bits from 0 to " + num + ":");
for (int i = 0; i <= num; ++i) {
System.out.print(ans[i] + " ");
}
System.out.println();
}
}