给定一个正整数N,计算从0到N中每个整数的1的数量,并将计数返回大小为N + 1的数组中。在O(n)时间内完成。
例如:
给定7,返回[0、1、1、2、1、2、2、3]。
当然,最简单的方法是创建一个循环来计算每个整数的1的数量,但这将需要O(kn)的时间,其中k是整数位数的大小。因此,要么有一种方法可以在O(1)的时间内计算整数的1的数量,要么有一种方法可以直接生成从0到N的计数。我相信两种方法都存在,但我想不出任何一种。
以下是一个简单易懂的方法,可以在O(n)时间内解决问题。假设你想知道数字k中有多少个二进制位为1,同时你已经知道数字0到k-1中有多少个二进制位为1。如果你能找到一种方法来清除数字k中任何一个二进制位为1,你就可以得到一个更小的数字(我们称之为m),而k中的二进制位数就等于m中的二进制位数再加上1。因此,只要我们能找到一种方法来清除数字k中的任何一个二进制位为1,我们就可以使用这种模式来解决问题:
result[0] = 0 // No bits set in 0
for k = 1 to n:
let m = k, with some 1 bit cleared
result[k] = result[m] + 1
有一个著名的位操作技巧,其中
k & (k - 1)
给定一个数字k,清除k中设置的最低位1,返回结果数字。假设机器可以以常数时间执行位运算(这通常是一个合理的假设),则此操作的时间复杂度为O(1)。下面的伪代码可以实现该功能:
result[0] = 0
for k = 1 to n:
result[k] = result[k & (k - 1)] + 1
这个操作每个数字只需要 O(1) 的时间,总共需要执行 O(n) 次,因此总的时间复杂度为 O(n)。
以下是另一种方法。举个例子,假设你已经知道了 0、1、2 和 3 这几个数字中二进制位上出现的次数,那么你就可以利用这些信息来计算出 4、5、6 和 7 中二进制位上出现的次数,因为这些数字的二进制表示都是在 0、1、2 和 3 的二进制表示前面加上一个 1 得到的。同样地,如果你已经知道了 0 到 7 中每个数字二进制位上出现的次数,那么你也可以通过在每个较小的数字二进制表示前面添加一个 1 来计算出 8、9、10、11、12、13、14 和 15 中二进制位上出现的次数。下面是相应的伪代码,为了简化起见,假设 n 是形如 2k - 1 的形式,但是它也很容易适应于一般的 n:
result[0] = 0;
for (int powerOfTwo = 1; powerOfTwo < n; powerOfTwo *= 2) {
for (int i = 0; i < powerOfTwo; i++) {
result[powerOfTwo + i] = result[i] + 1;
}
}
这个算法的时间复杂度为O(n)。要看到这一点,注意在这里所有循环的所有迭代中,数组中的每个条目都只写入了一次,并且确定要放入该插槽的值需要进行O(1)的工作。
k & (k-1)
视为一个单独的操作,在第一种方法(使用&)中也可以使用类似的论据。 - Henry更简单的回答:
令x为长度为k+1的数组,x[i]表示i中置位的数量。
x[0] = 0
for i=1:k
x[i] = x[i>>1] + i&1
我们从手动计算一些小数字的比特数开始。我们注意到n的比特数与之前结果之间存在一种递归关系:
n: | count(n): | recurrence:
==============================
0 | 0 |
1 | 1 |
------------------------------
10 | 1 | = count(0) + 1
11 | 2 | = count(1) + 1
------------------------------
100 | 1 | = count(0) + 1
101 | 2 | = count(1) + 1
110 | 2 | = count(10) + 1
111 | 3 | = count(11) + 1
...
在给定所有比特数的情况下,例如2 = 2¹,我们可以通过加1来计算出4 = 2²的比特数。在给定了4 = 2²的比特数的情况下,我们可以通过加1来计算出8 = 2³的比特数。我们将其推广到二的k次幂,并可以得出以下示例实现:
// Counts and returns number of enabled bits for integers 0 - n:
function count_bits(n) {
let count = new Array(n);
count[0] = 0;
for (let i = 1, j = 0, k = 1; i <= n; ++i, ++j) {
if (i == 2**k) { // k-th bit of i has been enabled
k++;
j = 0;
}
count[i] = count[j] + 1;
}
return count;
}
// Example:
console.log(count_bits(17).join());
i
的第 k 位来完成的,可以通过 i == 2**k
来重写它,也可以通过 i & 1 << k
来重写它,或者对于任意精度的 i
,可以通过随机数组访问来重写它。O(1)
的,那么总运行时复杂度为 O(n)
。O(1)
,除非复制 count[i] = count[j] + 1
花费的时间超过常数时间。由于这恰恰是针对任意大的整数的情况,因此我们的运行时复杂度为 O(n log(log(n)))
,因为我们需要 O(log(log(n)))
的空间来存储 n 的位计数。