题目:输入一个非负数n,请计算0到n之间每个数字的二进制形式中1的个数,并输出一个数组、例如,输入的n位4,由于0,1,2,3,4的二进制形式中1的个数分别为0,1,1,2,1,因此输出数组[0,1,1,2,1]。
方法一:简单计算每个整数的二进制形式中1的个数
该问题关键所在其实是求解一个整数i的二进制形式中1的个数。对于求解该问题,我们需要知道一个知识点,i&(i-1)会将i最右边的1变为0(其中i和i-1均为整数二进制)。因为整数i减去1后,其最右边的1会变成0,若该“1”右边有0,则全部变成1,而该“1”左边保持不变。例如:
1 0001
2 0010
3 0011
4 0100
由此,我们发现一个整数二进制i能做几次这样的&运算,那么它就有几个1。这就是方法一的核心。
//j与j-1的与运算会消除最右边的1
public static int[] countBits1(int num) {
//result存放结果并充当计数的作用
int[] result = new int[num + 1];
for (int i = 0; i <= num; i++) {
int j = i;
while (j != 0) {
result[i]++;
j = j & (j - 1);
}
}
return result;
}
如果一个整数有k位,那么可能有k个1,while循环将执行O(k)次,时间复杂度为O(nk)。
方法二:根据“i&(i-1)”计算i的二进制形式中1的个数
由“i&(i-1)将i的二进制形式中最右边的1变成0”这句话进一步思考,整数i的二进制形式中1的个数比“i&(i-1)"的二进制形式中1的个数多1。
//整数i的二进制中1的个数比i&(i-1)的二进制形式中的1的个数多1
public static int[] countBits2(int num) {
int[] result = new int[num + 1];
for (int i = 1; i <= num; i++) {
result[i] = result[i & (i - 1)] + 1;
}
return result;
}
不管i有几位,该法只需O(1)的时间就可计算出i的二进制中1的个数,因此时间复杂度是O(n)。
方法三:根据”i/2“计算i的二进制形式中1的个数
如果i是偶数,那么i相当与”i/2“左移一位的结果,因此,二者二进制形式中1的个数相同。如果i是奇数,那么i相当与”i/2“左移一位之后再将最右边一位设为1的结果,因此,奇数i的二进制中1的个数比i/2的多1。
//i为偶数,i相当于i/2左移一位的结果
//i为奇数,i相当于i/2左移一位后再将最右边一位设为1
//i & 1用来计算 i % 2
public static int[] countBits3(int num){
int[] result = new int[num + 1];
for (int i = 1; i <= num; i++) {
result[i] = result[i >> 1] + (i & 1);
}
return result;
}
位运算比除法运算和求余运算更高效,所以用i>>1计算i/2,i&1计算i%2。该方法时间复杂度也为O(n)。
完整代码
import java.util.Scanner;
public class CountBits {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
Integer i = Integer.valueOf(s);
int[] arr1 = countBits1(i);
print(arr1);
int[] arr2 = countBits2(i);
print(arr2);
int[] arr3 = countBits3(i);
print(arr3);
}
private static void print(int[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
System.out.print(arr[i]);
} else {
System.out.print(arr[i] + " ");
}
}
System.out.println("]");
}
//n与n-1的与运算会消除最右边的1
public static int[] countBits1(int num) {
int[] result = new int[num + 1];
for (int i = 0; i <= num; i++) {
int j = i;
while (j != 0) {
result[i]++;
j = j & (j - 1);
}
}
return result;
}
//整数i的二进制中1的个数比i&(i-1)的二进制形式中的1的个数多1
public static int[] countBits2(int num) {
int[] result = new int[num + 1];
for (int i = 1; i <= num; i++) {
result[i] = result[i & (i - 1)] + 1;
}
return result;
}
//i为偶数,i相当于i/2左移一位的结果
//i为奇数,i相当于i/2左移一位后再将最右边一位设为1
//i & 1用来计算 i % 2
public static int[] countBits3(int num){
int[] result = new int[num + 1];
for (int i = 1; i <= num; i++) {
result[i] = result[i >> 1] + (i & 1);
}
return result;
}
}