题目描述

image.png

解题思路

Brian Kernighan 算法

核心思想就是使用 n&(n-1) 将最低位的1变为0,直到n等于0时停止,记录操作的次数,例如10001,相与一次之后就为10000,在相与一次就为00000,一共操作了2次,所以一共2个1。

  1. // Brian Kernighan 算法
  2. // 对于任意整数 x,令 x=x~\&~(x-1)x=x & (x−1),
  3. // 该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。
  4. public int[] countBits(int n) {
  5. int[] bits = new int[n + 1];
  6. for (int i = 0; i <= n; i++) {
  7. bits[i] = countOnes(i);
  8. }
  9. return bits;
  10. }
  11. public int countOnes(int x) {
  12. int count = 0;
  13. while (x != 0) {
  14. x &= (x - 1);
  15. count++;
  16. }
  17. return count;
  18. }

image.png

动态规划——最高有效位

  • 如果 i & (i−1)=0,则令 highBit=i,更新当前的最高有效位。(高位必须为2的n次幂,通过 i & (i−1)=0可以得出是否是)
  • i 比 i−highBit 的「一比特数」多 1,由于是从小到大遍历每个整数,因此遍历到 i 时,i−highBit 的「一比特数」已知,令bits[i]=bits[i−highBit]+1。(此时后面的+1是高位的1)

例如:bits[10001] = bits[1] + 1,bits[10011] = bits[11] + 1

  1. // 动态规划——最高有效位
  2. public int[] countBits(int n) {
  3. int[] bits = new int[n + 1];
  4. int highBit = 0; // 表示最高位
  5. for (int i = 1; i <= n; i++) {
  6. // 判断是否是2的幂
  7. if ((i & (i - 1)) == 0) {
  8. highBit = i;
  9. }
  10. bits[i] = bits[i - highBit] + 1;
  11. }
  12. return bits;
  13. }

image.png

动态规划——最低有效位

最低有效位也就是通常的移位操作。
当向右移动一位之后,此时数字x变为x / 2,如果:

  • x为奇数:此时bits[x]=bits[x/2]+1
  • x为偶数:此时bits[x]=bits[x/2]

也就是统计最后一位的次数。所以可以写为bits[x]=bits[x/2]+x&1。

  1. // 动态规划——最低有效位
  2. public int[] countBits(int n) {
  3. int[] bits = new int[n + 1];
  4. for (int i = 0; i <= n; i++) {
  5. bits[i] = bits[i >> 1] + i % 2;
  6. }
  7. return bits;
  8. }

image.png

动态规划——最低设置位

通过第一个解法可以知道n&(n-1)可以将最低为的1变为0,所以此时bits[n]=bits[n&(n-1)]+1,因为此时只有最后一位1变为0,例如1010就等于1000的1的个数加一。

  1. // 方法四:动态规划——最低设置位
  2. public int[] countBits(int n) {
  3. int[] bits = new int[n + 1];
  4. for (int i = 1; i <= n; i++) {
  5. bits[i] = bits[i & (i - 1)] + 1;
  6. }
  7. return bits;
  8. }