我发现了这个问题here,但这不是我要找的答案。因此,再次发布。
表格的功能:
F( N ) = rank
表示给定十进制表示中的数字N
,其等级为:
Starting from 0 to N, how many numbers are there with same number of set bits in its
binary representation.
我将通过一个例子来说明它。
N = 6 ( 0110 )
Its rank is 3.
1. 3 ( 0011 )
2. 5 ( 0101 )
3. 6 ( 0110 )
现在,给出一个数字,找到它的排名。
显而易见的方法是从0开始,检查每个数字的设定位数,直到N-1
。
问题是:
有没有logN解决方案?
答案 0 :(得分:7)
是的,有一个log n
解决方案。
设n
设置k
位,最高位位于p
位置(开始计算0的位置,2^p <= n < 2^(p+1)
)。然后有pCk
(二项式系数,也是choose(p,k)
)方法将k
位放在位置0, ..., p-1
中,所有这些都给出了具有k
个设置位的数字小于n
。如果是k == 1
,那就是它,否则会有k
个设置位的数字和p
- 位设置小于n
要考虑的数字。可以通过确定n - 2^p
的等级来计算这些。
代码(不是最优的,做了一些不必要的重新计算,并不是为避免溢出而做的所有事情):
unsigned long long binom(unsigned n, unsigned k) {
if (k == 0 || n == k) return 1;
if (n < k) return 0;
if (k > (n >> 1)) k = n-k;
unsigned long long res = n, j;
// We're not doing all we can to avoid overflow, as this is a proof of concept,
// not production code.
for(j = 2; j <= k; ++j) {
res *= (n+1-j);
res /= j;
}
return res;
}
unsigned popcount(unsigned long long n) {
unsigned k = 0;
while(n) {
++k;
n &= (n-1);
}
return k;
}
unsigned long long rank(unsigned long long n) {
if (n == 0) return 1;
unsigned p = 0, k = popcount(n);
unsigned long long mask = 1,h = n >> 1;
while(h > 0) {
++p;
h >>= 1;
}
mask <<= p;
unsigned long long r = binom(p,k);
r += rank(n-mask);
return r;
}
在0 <= n < 10^8
的循环中测试,以检查错误,但未发现任何不匹配。
检查输出here。
答案 1 :(得分:0)
可以通过组合和排列技术来解决。
F(N)=等级
首先找到表示N所需的位数。在二进制表示中,数字可以构造如下:
N = cn 2^n + .... + c3 2^2 + c2 2^1 + c1 2^0
现在,为了在上面的等式中找到n
(或数字的二进制表示中的位数),我们可以假设它将是floor(log2(N)) + 1
。例如,考虑任何数字,比如说118
然后它可以用floor(log2(118))+ 1 = 7位表示。
n = floor(log2(118)) + 1;
以上公式仅为O(1)
。现在,我们需要找到一个数字的二进制表示中有多少1。考虑使用伪代码来完成这项工作:
function count1(int N) {
int c = 0;
int N' = N;
while(N' > 0) {
N' -= 2^(floor(log2(N')));
c++;
}
}
以上伪代码为O(logN)
。我在MATLAB中编写了一个小脚本来测试我上面的伪代码,这里是结果。
count1(6) = 2
count1(3) = 2
count1(118) = 5
完美,现在我们在这些位中有多个位数和1位数。现在,可以应用简单的组合和置换来查找数字的等级。首先假设,n
是表示数字所需的位数,c
是数字的位表示中的1的数量。因此,排名将由下式给出:
r = n ! / c ! * (n - c) !
编辑:正如DSM所建议的,我已经纠正了找到正确RANK的逻辑。想法是从排列中删除所有不需要的数字。所以添加了这段代码:
for i = N + 1 : 2^n - 1
if count(i) == c
r = r - 1;
end
end
我编写了一个MATLAb脚本,使用上述方法查找数字的等级:
function r = rankN(N)
n = floor(log2(N)) + 1;
c = count(N);
r = factorial(n) / (factorial(c) * factorial(n - c));
% rejecting all the numbers may have included in the permutations
% but are unnecessary.
for i = N + 1 : 2^n - 1
if count(i) == c
r = r - 1;
end
end
function c = count(n)
c = 0;
N = n;
while N > 0
N = N - 2^(floor(log2(N)));
c = c + 1;
end
以上的算法是O(1) + O(logN) + O(1) = O(logN)
。输出是:
>> rankN(3)
ans =
1
>> rankN(4)
ans =
3
>> rankN(7)
ans =
1
>> rankN(118)
ans =
18
>> rankN(6)
ans =
3
注意:0
的等级始终为1
,因为0
的上述方法将失败,因为log2(0)
未定义。
答案 2 :(得分:0)
这是一个相当高效的O(logN)实现,每个步骤并行执行多个添加:
unsigned int countBits( unsigned int bits )
{
bits = ( bits & 0x55555555 ) + ( ( bits >> 1 ) & 0x55555555 );
bits = ( bits & 0x33333333 ) + ( ( bits >> 2 ) & 0x33333333 );
bits = ( bits + ( bits >> 4 ) ) & 0x0f0f0f0f;
bits += bits >> 8;
return ( bits + ( bits >> 16 ) ) & 63;
}
它从16个2位加法开始,然后进行8个4位加法,依此类推。它可以通过使用更长的掩码和一个额外的步骤扩展为使用64位整数:
unsigned int countBits( unsigned long long bits )
{
bits = ( bits & 0x5555555555555555L ) + ( ( bits >> 1 ) & 0x5555555555555555LL );
bits = ( bits & 0x3333333333333333L ) + ( ( bits >> 2 ) & 0x3333333333333333LL );
bits = ( bits + ( bits >> 4 ) ) & 0x0f0f0f0f0f0f0f0fLL;
bits += bits >> 8;
bits += bits >> 16;
return (unsigned int) ( bits + ( bits >> 32 ) ) & 127;
}