前n个数字二进制形式中的1的个数

80 阅读3分钟

第一种,简单计算每个整数的二进制形式中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)

截屏2025-07-01 22.21.51.png

结果:j=8 (1000)

你看,最右边那个 1 被消掉了(原来在第1位的 1

第二步:

现在 j=8 (1000)

  • j-1=7 (0111)

截屏2025-07-01 22.22.10.png

结果: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();
    }

}

截屏2025-07-01 21.34.15.png

我们输入的是 num=5

所以计算:

  • result[0]
  • result[1]
  • result[2]
  • result[3]
  • result[4]
  • result[5]

计算每个数的二进制表示中 1 的个数:

数字 i二进制表示有几个 1
000
111
2101
3112
41001
51012

所以结果数组就是:

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();
    }

}

截屏2025-07-01 22.39.41.png

截屏2025-07-01 22.53.50.png

第三种,根据“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();
    }

}

截屏2025-07-01 21.40.31.png