Problem
Given an integer n
, return an array ans
of length n + 1
such that for each i
(0 <= i <= n
), ans[i]
is the number of 1
’s in the binary representation of i
.
https://leetcode.com/problems/counting-bits/
Example 1:
Input:
n = 2
Output:[0,1,1]
Explanation:
1
2
3 0 --> 0
1 --> 1
2 --> 10
Example 2:
Input: n = 5
Output: [0,1,1,2,1,2]
Explanation:
1
2
3
4
5
6 0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
Constraints:
0 <= n <= 10⁵
Follow up:
- It is very easy to come up with a solution with a runtime of
O(n log n)
. Can you do it in linear timeO(n)
and possibly in a single pass? - Can you do it without using any built-in function (i.e., like
__builtin_popcount
in C++)?
Test Cases
1 | class Solution: |
1 | import pytest |
Thoughts
计算一个整数 n 的二进制中 1
的个数,就循环右移直到数字降为 0,过程中记录最低位是 1
的次数,时间为 O(log n)
。
对所有从 0 到 n 的整数都算一次,总时间是 O(n log n)
。
Code
1 | class Solution: |
Follow Up - O(n)
连续计算的时候,可以利用已有的结果加速。
一个思路是看当从 n - 1
变到 n 时,减少了几个 1
,增加了几个 1
。
比如 n = 0b1001000
,那么 n - 1 = 0b1000111
。二者二进制位没有变化的部分可以用 bit-and 计算,即 common = n & (n-1) = 0b1000000
。
用 common
分别与两个数计算 bit-xor,结果就是变化的部分。
如 xor(common, n-1) = 0b111
,这些 1
都被去掉了。xor(common, n) = 0b1000
,这些 1
是新加入的。
令 f(n)
表示整数 n 的二进制中 1
的个数,那么:
f(n) = f(n-1) - f(xor(common, n-1)) + f(xor(common, n))
。
一般 xor(common, n-1)
和 xor(common, n)
都小于 n。唯独当 n 是 2 的幂时,xor(common, n)
等于 n,可以对这个情况做特判,f(2ⁱ) = 1
。
这样对于每个数,都只需要常数时间进行计算和查表,总时间是 O(n)
。
1 | class Solution: |
Faster O(n)
上边的方法还是太繁琐(且慢),而且「n 是 2 的幂」这种特例容易被忽略而造成 bug。
实际上对于一个 d 位的二进制数 n,最高位是 1
,剩下的 d - 1
位构成了 m = n - 2ᵈ⁻¹
,显然 0 <= m < n
。如果 m 中 1
的个数已知,那么显然 f(n) = 1 + f(m)
。
只剩下一个问题就是常数时间确定 d,或者 P = 2ᵈ⁻¹
。在遍历整数的过程中,很容易跟踪 P 的变化。初始 P = n = 1
,当 n 递增到 P * 2
时,就可以更新 P = P * 2
。
总的时间复杂度也是 O(n)
,但系数要小得多。
1 | class Solution: |
三种算法的实际运行时间对比:
1 | [1:n log n] n = 10: 4.994 μs |