给定一个整数 N (1 ≤ N ≤ 10^15),我们的任务是统计 1 到 N 之间所有整数的二进制表示中,数字 1 出现的总次数。
示例:
> 输入: N = 7
> 输出: 12
> 说明:
>
> 1 的二进制表示:001 -> 置位数 = 1
> 2 的二进制表示:010 -> 置位数 = 1
> 3 的二进制表示:011 -> 置位数 = 2
> 4 的二进制表示:100 -> 置位数 = 1
> 5 的二进制表示:101 -> 置位数 = 2
> 6 的二进制表示:110 -> 置位数 = 2
> 7 的二进制表示:111 -> 置位数 = 3
>
> 因此,1 的总个数为 1 + 1 + 2 + 1 + 2 + 2 + 3 = 12。
>
> 输入: N = 5
> 输出: 7
> 说明:
>
> 1 的二进制表示:001 -> 置位数 = 1
> 2 的二进制表示:010 -> 置位数 = 1
> 3 的二进制表示:011 -> 置位数 = 2
> 4 的二进制表示:100 -> 置位数 = 1
> 5 的二进制表示:101 -> 置位数 = 2
>
> 因此,1 的总个数为 1 + 1 + 2 + 1 + 2 = 7。
解题思路: 为了解决这个问题,我们可以遵循以下思路:
如果输入的数字 N 的形式为 2^b – 1 (例如 1, 3, 7, 15… 等),那么置位数的总数就是 b 2^(b-1)。这是因为对于 0 到 (2^b) – 1* 之间的所有数字,如果我们对列表进行补码和反转,最终会得到相同的列表(即一半的位是开启的,一半是关闭的)。
如果数字并非所有位都为 1,那么我们会找到某个位置 m 作为最左侧置位的位置。该位置上的置位数量为 n – (1 << m) + 1。剩下的置位分为两部分:
- 处于 (m-1) 位置上的位,直到最左侧的位变为 0 的那一点,以及
- 处于该点之下的 2^(m-1)个数字,这对应上述的闭式公式。
让我们以数字 6 为例:
0|0 0
0|0 1
0|1 0
0|1 1
-|--
1|0 0
1|0 1
1|1 0
最左侧的置位位于位置 2(位置从 0 开始计算)。如果我们将其掩码掉,剩下的是 2(最后一行右侧部分的“1 0”)。所以,第 2 个位置(左下角的框)的位数是 3(即 2 + 1)。来自 0-3 的置位(右上角的框)是 2*2^(2-1) = 4。右下角的框包含我们尚未计算的剩余位,即所有数字直到 2 的置位数(右下角框中最后一个条目的值),这部分可以通过递归计算得出。
下面是该算法的实现:
C++
INLINECODE82ca7ef3`INLINECODE5b627a3a`