编写一个算法,找到 F(n)
,即在给定值 n 的情况下,从 1 到 n 中所有数字中设置为 1 的位数。
复杂度应为 O(log n)
例如:
1: 001
2: 010
3: 011
4: 100
5: 101
6: 110
所以
F(1) = 1,
F(2) = F(1) + 1 = 2,
F(3) = F(2) + 2 = 4,
F(4) = F(3) + 1 = 5,
etc.
我只能设计一个O(n)
的算法。
编写一个算法,找到 F(n)
,即在给定值 n 的情况下,从 1 到 n 中所有数字中设置为 1 的位数。
复杂度应为 O(log n)
例如:
1: 001
2: 010
3: 011
4: 100
5: 101
6: 110
F(1) = 1,
F(2) = F(1) + 1 = 2,
F(3) = F(2) + 2 = 4,
F(4) = F(3) + 1 = 5,
etc.
我只能设计一个O(n)
的算法。
0
,而最后8个则有一个1
。因此,例如要计算F(12)
,我们可以取F(7)
并加上8、9、10、11和12的置位数。但这与计算0、1、2、3和4的置位数是相同的,再加上每个数字额外的一个置位数!9 1 001 现在每个数字都多了一个“1”! 10 1 010 11 1 011 12 1 100
因此,对于8 <= n <= 15,F(n) = F(7) + F(n-8) + (n-7)。同样地,我们可以注意到对于4 <= n <= 7,F(n) = F(3) + F(n-4) + (n-3);对于2 <= n <= 3,F(n) = F(1) + F(n-2) + (n-1)。一般来说,如果我们设置a = 2^(floor(log(n))),则F(n) = F(a-1) + F(n-a) + (n-a+1)。
这并不能完全给我们一个O(log n)的算法;不过,实现起来很简单。如果a = 2^x,则注意到上面的表中对于a-1,第一个位恰好被设定了a/2次,第二个位被设定了a/2次,第三个位……一直到第x个位。因此,F(a-1) = x*a/2 = x*2^(x-1)。在上述公式中,这给了我们: F(n) = x*2^(x-1) + F(n-2^x) + (n-2^x+1) 其中x = floor(log(n))。每一轮计算F实际上都会删除MSB(最高有效位);因此,这是一个O(log(n))的算法。
2^x
是一个O(1)
操作*(在现实世界的计算机上,使用左移是这样的)*,那么确实是O(log n)
。 - BlueRaja - Danny PflughoeftO(1)
的时间复杂度获得x
。实际上,在现代的x86和x64机器上,这是可以做到的:它被称为BSR指令*(在GCC中是__builtin_clz;在VC++中是_BitScanReverse*)。无论哪种情况,我真的不认为这个小问题应该被downvote :| - BlueRaja - Danny Pflughoeft0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
第0
位(LSB)1
位每两位出现一次(垂直查看),因此设置位数= n/2 +
(如果n的第0位为1
,则为1
,否则为0
)第1
位位置中的设置位数= (n/4*2)+1
(因为第1
位是设置的,否则为0
)第2
位:4
个连续的1s
每8
位出现一次(这有点棘手)
第2
位中的设置位数= (n/8*4)+1
(因为第2
位设置,否则为0
)+ ((n%8)%(8/2))
最后一个术语是包括在第一个(n/8)
位组外的1s
的数量(14/8 = 1
仅考虑1
组即8
位中的4
个设置位。我们需要包括在最后的14-8 = 6
位中发现的1s
)第3
位:每16
位出现8
个连续的1(类似于上面)
第3
位位置中的设置位数= (n/16*8)+1
(因为第3
位是设置的,否则为0
)+ ((n%16)%(16/2))
因此,我们对于数字n
的每个位进行O(1)
计算。
一个数字包含log2(n)
位。因此,当我们遍历上述所有位置并在每个步骤中添加所有设置的位时,我们可以在O(logn)
步骤中得到答案。
快速搜索序列F的值会导致这个整数序列 http://oeis.org/A000788。
我在那里发现了一个公式: a(0) = 0, a(2n) = a(n)+a(n-1)+n, a(2n+1) = 2a(n)+n+1 (由于我只是从oeis复制了公式,所以a与F相同)
这个公式可以用来在log(n)时间内计算a(n)。
这是我的C++代码示例:
memset(cache, -1, sizeof(cache))
cache[0] = 0
int f(int n)
if cache[n] != -1 return cache[n];
cache[n] = n % 2 ? (2 * f(n / 2) + n / 2 + 1) : (f(n / 2) + f(n / 2 - 1) + n / 2)
这个问题可以使用基于位掩码的DP(动态规划)算法来解决。
自底向上的基本想法是,我们将直接访问当前数字值的set bit数,并通过与 1 的 and 操作来检查该当前数字的最后一位是否被设置。
current_number/2或current_number>>1基本上会删除此current_number的最后一位,因此为了将该位包含在我们的计数中,我们必须使用 & 运算手动检查该数字的最后一位。
以下是计算数字i中set bits数量的表达式 dp[i]=dp[i>>1]+(i&1)
如果您在解决这个问题时仍然遇到困难,那么您可以参考以下视频以获得更好的解释:-
假设需要 n
个比特位,记为 k
。
对于 0,...,2^(k-1)-1
中的每一个数,每一位恰好出现在其中一半的数字中,因此到目前为止有 (k-1)*2^(k-2)
个比特位被设置为 "1"。我们只需要检查大于 2^(k-1)-1
的数字中有哪些比特位被设置了 "1"。
对于那些大于 2^(k-1)-1
的数字,最高位有 n-2^(k-1)-1
个比特位被设置为 "1"。
因此,我们可以得出递归函数:
f(n) = (k-1)*2^(k-2) + n-(2^(k-1)-1) + f(n-(2^(k-1)))
^ ^ ^
first MSBs recursive call for
2^(k-1)-1 n-2^(k-1) highest numbers
numbers
其中,base的值为f(0) = 0
和f(2^k) = k*2^(k-1) + 1
[如前所述,我们已经知道了对于2^(k-1)-1
有多少位是置位的,我们只需要再加上1 - 即为2^k
的最高位]
由于每次迭代发送到f
的值至少减半,因此我们总共需要进行O(logn)
次迭代。
F(4)
。你会得到 4
,而不是正确的答案 5
。 - BlueRaja - Danny Pflughoeft这是我的解决方案。时间复杂度:O(Log n)
public int countSetBits(int n){
int count=0;
while(n>0){
int i= (int)(Math.log10(n)/Math.log10(2));
count+= Math.pow(2, i-1)*i;
count+= n-Math.pow(2, i)+1;
n-= Math.pow(2, i);
}
return count;
}
简短而精炼!
public static int countbits(int num){
int count=0, n;
while(num > 0){
n=0;
while(num >= 1<<(n+1))
n++;
num -= 1<<n;
count += (num + 1 + (1<<(n-1))*n);
}
return count;
}//countbis
O((log n)^2)
而不是 O(log n)
。 - BlueRaja - Danny Pflughoeft不确定是否晚了回复,但以下是我的发现。
尝试用以下方法解决问题,对于数字N,可以计算每个位号(从LSB到MSB,例如LSB从位号1开始,并且通过下一个位值递增)设置的位数为(N /(2的位号次方)*(2的位号次方-1)+ {(N%(2的位号次方)) - [(2的位号次方-1)- 1]}
已经在C/C ++中编写了递归函数,请检查。我不确定,但我认为它的复杂性是log(N)。传递函数2个参数,要计算位的数字(no)和第二个从LSB开始计数的起始值1。
int recursiveBitsCal(int no, int bitno){
int res = int(no/pow(2,bitno))*int(pow(2,bitno-1));
int rem1 = int(pow(2,bitno-1)) -1;
int rem = no % int(pow(2,bitno));
if (rem1 < rem) res += rem -rem1;
if ( res <= 0 )
return 0;
else
return res + recursiveBitsCal(no, bitno+1);
}