LeetCode LCR 003. 比特位计数
题目描述


题意分析
对每个整数
i = 0..n,统计其二进制表示中一的个数,按数值顺序返回长度为n + 1的数组。要求的是整段范围的答案,不只是n自身的计数。
n可以为零,此时仍要返回包含零计数的一项。进阶要求不用内置位计数函数,通过一趟线性扫描求出全部结果,因此可以让较大数复用此前已算出的较小数状态。
解法:删除最低位 1 的递推
核心思路
[!blue]
令
f[i]表示整数i的二进制中一的个数,初始f[0] = 0。要在常数时间求出新状态,需要把i转成一个已经求解、且置位数量与它存在确定关系的更小数。对正数
i,减一会把最低的一位从一变零,同时把它右边原有的零全部变成一,更高位保持不变。再与原数按位与,右侧因为原来全为零仍然为零,最低的那一位被清除,其余高位不变。因此i & (i - 1)恰好比i少一个一。得到转移
f[i] = f[i & (i - 1)] + 1。清掉一个置位会使数值严格减小,所以按i从小到大处理时,依赖下标已经计算完成,不需要继续循环剥掉其他位。每个新数只做一次按位运算和一次查表,前面完成的位计数通过数组被复用。零不能应用“删除一位”的转移,因为它没有任何一,保留初始零状态即可;结果数组也就是状态数组,无需第二份存储。
解题步骤
- 创建长度为
n + 1的零数组,保留f[0] = 0。- 从
i = 1递增到n,计算去掉最低置位后的下标i & (i - 1)。- 将该下标的计数加一写入
f[i]。- 返回整个数组,包含零到
n的全部结果。
代码实现
class Solution {
public int[] countBits(int n) {
int[] f = new int[n + 1];
for (int i = 1; i <= n; ++i) {
// i & (i - 1) 抹掉最低位的 1,它一定小于 i,答案已算好。
f[i] = f[i & (i - 1)] + 1;
}
return f;
}
}
func countBits(n int) []int {
f := make([]int, n+1)
for i := 1; i <= n; i++ {
// i & (i - 1) 抹掉最低位的 1,它一定小于 i,答案已算好。
f[i] = f[i&(i-1)] + 1
}
return f
}
复杂度分析
- 时间复杂度:$O(n + 1)$,创建并填充长度为
n + 1的数组,每个非零状态只做常数次操作。- 空间复杂度:结果及状态数组占 $O(n + 1)$;若不计必须返回的数组,额外空间为 $O(1)$。
关键点总结
[!green]
i & (i - 1)清除最低的一位,计数恰好减少一,而不是任意改变数值。- 依赖下标严格更小,使递增填表可以直接复用已完成答案。
- 状态下标就是对应整数,数组要包含
n本身。- 一趟扫描的关键是复用计数,不是对每个整数重新统计所有位。
易错点总结
[!yellow]
- 数组长度写成
n会漏掉最后一个数,n = 0时也失去应返回的零项。- 从零开始套用转移,会把
f[0]错误地更新成一,应从一开始。- 反向填表可能读取未完成的较小状态,无法保证递推正确。
- 位运算要显式保留必要括号,避免把减法、位与及数组下标的结合关系写错。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 191. 位1的个数 | 简单 | 原题只统计一个数的1,本题连续求0到n,可复用较小状态避免逐个重新计数。 |
| 461. 汉明距离 | 简单 | 异或先定位两数不同位,再用位计数得到汉明距离。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!