(LeetCodeHot100)338. 比特位计数——counting-bits

338. 比特位计数counting-bits

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

示例 1:

1
2
3
4
5
6
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10

示例 2:

1
2
3
4
5
6
7
8
9
输入:n = 5
输出:[0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

提示:

  • 0 <= n <= 105

  • 此题没有见过,直接学习
  • (有相应的内置函数用于计算给定的整数的二进制表示中的 1 的数目,例如Integer.bitCount)

要解决这个问题,我们需要计算从 0n 的每个整数的二进制表示中 1 的个数,并返回一个包含这些计数的数组。可以利用动态规划结合二进制数的特性高效求解,避免对每个数单独计算(时间复杂度可优化到 O(n))。

核心思路:利用二进制数的奇偶性和右移特性

  • >>右移位运算符,作用是将一个数的二进制表示向右移动指定的位数,并返回移动后的结果(以十进制形式呈现)

观察二进制数的规律:

  • 若一个数 i偶数,则 i 的二进制表示和 i/2 的二进制表示完全相同(只是末尾多了一个 0),因此 1 的个数也相同(例如 4(100)2(10) 都只有 1 个 1)。
  • 若一个数 i奇数,则 i 的二进制表示比 i-1(偶数)多一个 1(末尾的 0 变成 1),因此 1 的个数是 i-1 的计数加 1(例如 5(101)4(100) 多一个 1,计数为 1+1=2)。

结合右移操作(i >> 1 等价于 i/2),可以推导出递推公式:

  • i 为偶数:count[i] = count[i >> 1]
  • i 为奇数:count[i] = count[i >> 1] + 1

简化后可统一为:count[i] = count[i >> 1] + (i & 1)i & 1 用于判断奇偶,奇数为 1,偶数为 0)。

代码实现

  • 当你用 new int[n + 1] 创建数组时,Java 会自动为数组中的每个元素分配默认值
    • 整数类型(int)的默认值是 0
    • 布尔类型默认 false,引用类型默认 null 等。
1
2
3
4
5
6
7
8
9
10
class Solution {
public int[] countBits(int n) {
int[] ans = new int[n + 1]; // 存储 0~n 的每个数的 1 的个数
for (int i = 1; i <= n; i++) {
// 利用二进制特性:i 的 1 的个数 = i/2 的 1 的个数 + (i 是否为奇数)
ans[i] = ans[i >> 1] + (i & 1);
}
return ans;
}
}

逻辑验证(以 n=5 为例)

  • i=0:初始化为 0(二进制 0 有 0 个 1)。
  • i=11 >> 1 = 01 & 1 = 1ans[1] = ans[0] + 1 = 0 + 1 = 1
  • i=22 >> 1 = 12 & 1 = 0ans[2] = ans[1] + 0 = 1 + 0 = 1
  • i=33 >> 1 = 13 & 1 = 1ans[3] = ans[1] + 1 = 1 + 1 = 2
  • i=44 >> 1 = 24 & 1 = 0ans[4] = ans[2] + 0 = 1 + 0 = 1
  • i=55 >> 1 = 25 & 1 = 1ans[5] = ans[2] + 1 = 1 + 1 = 2

最终结果为 [0,1,1,2,1,2],与示例一致。

复杂度分析

  • 时间复杂度O(n),只需遍历一次 0~n
  • 空间复杂度O(n),需要一个长度为 n+1 的数组存储结果。