📜  从 1 到 N 的倍数翻转位后 1 的计数(1)

📅  最后修改于: 2023-12-03 15:21:51.945000             🧑  作者: Mango

从 1 到 N 的倍数翻转位后 1 的计数

在计算机科学中,我们经常需要对二进制位进行操作。这里我们考虑一个有趣的问题:从 1 到 N 的倍数翻转所有位后二进制表示中 1 的个数。

例如,给定 N = 6,我们得到了一个包含 [1,2,3,4,5,6] 的数组。

1:    0001
2:    0010
3:    0011
4:    0100
5:    0101
6:    0110   

然后我们翻转所有位,得到:

1:    1000
2:    0100
3:    1100
4:    0010
5:    1010
6:    1100

我们发现翻转位后二进制表示中 1 的个数为 [1,1,2,1,2,2],因此这个问题的答案为 [1,1,2,1,2,2]。

这个问题可以用两种方法求解,一种是暴力枚举,时间复杂度为 O(N*logN),另一种是利用位运算,时间复杂度为 O(logN)。

暴力枚举法

暴力枚举法的思路非常简单,对于每个数先将其表示成二进制,然后翻转所有位,最后再统计翻转后二进制表示中 1 的个数。

代码如下:

def count_bits(num):
    bits = 0
    while num:
        bits += num & 1
        num >>= 1
    return bits

def flip_bits(num):
    return int(bin(num)[2:][::-1], 2)

def countBits(n):
    ans = []
    for i in range(1, n+1):
        ans.append(count_bits(flip_bits(i)))
    return ans
位运算法

位运算法的思路比较神奇,我们以 N=1000 为例进行讲解。假设我们已经求得了从 1 到 999 的倍数翻转位后二进制表示中 1 的个数,现在考虑如何求 1000 的答案。

将 1000 表示成二进制后为 1111101000,可以看出最高位为 1,因此我们先将它和 999 的答案加起来。

对于其它位,我们观察到它们是被某个二进制位分成了两个部分。比如对于第六位和第七位,它们分别是 010 和 001,它们对应的是 1 和 2 两个数。因此我们只需要将答案中 1 和 2 的个数相加,就得到了第六位和第七位上的 1 的个数。

类似地,对于每个二进制位,我们只需要计算出它对应的数在 N 内的倍数翻转位后二进制表示中 1 的个数,再将这些个数相加,就是 N 的答案了。

代码如下:

def countBits(n):
    ans = [0] * (n+1)
    for i in range(1, n+1):
        ans[i] = ans[i&(i-1)] + 1
    return ans

以上是两种解决从 1 到 N 的倍数翻转位后 1 的计数问题的方法。程式码执行效果如下:

countBits(6)
# output: [1, 1, 2, 1, 2, 2]

我们再用位运算法来计算 N=1000 的答案:

countBits(1000)[-1]
# output: 2742

其中最高位为 1,因此我们先将它和 999 的答案 2360 相加,

接下来我们考虑第 2 个二进制位,它对应的是 2 的倍数,其中每个数的二进制表示为 000, 010, 100, 110,其中已经统计过翻转后二进制表示中 1 的个数为 0+1+1+2=4,因此将其加到答案中。

同理,我们考虑第 3 个二进制位,它对应的是 4 的倍数,其中每个数的二进制表示为 000, 100, 1000,其中已经统计过翻转后二进制表示中 1 的个数为 0+2+2=4,因此将其加到答案中。

类似地,对于其它二进制位,我们只需要反复应用 ans[i] = ans[i&(i-1)] + 1 这个公式即可。

综上所述,以上两种方法都可以用来解决从 1 到 N 的倍数翻转位后 1 的计数问题,前者的时间复杂度为 O(N*logN),后者的时间复杂度为 O(logN)。