我建议将此算法从以 2 为基数调整为以 10 为基数:
范围内整数的二进制补码二进制表示中 1 的数量
结果算法为 O(log N)。
方法是编写一个简单的递归函数count(n)
,从 1 到 计数零n
。
关键的观察是,如果 N 以 9 结尾,例如:
123456789
您可以将 0 到 N 的数字分成 10 个大小相等的组。第 0 组是以 0 结尾的数字。第 1 组是以 1 结尾的数字。第 2 组是以 2 结尾的数字。依此类推,一直到第 9 组,即所有以 9 结尾的数字。
除了第 0 组之外,每个组都count(N/10)
对总数贡献了零位,因为它们都没有以零结尾。第 0 组贡献count(N/10)
(计算除最后一位以外的所有数字)加上N/10
(从最后一位数中计算零)。
由于我们是从 1 到 N,而不是从 0 到 N,这个逻辑对于个位数的 N 是不成立的,所以我们只是将其作为一种特殊情况来处理。
[更新]
到底是什么,让我们概括并定义数字在从 1 到count(n, d)
的数字中出现的次数。d
n
/* Count how many d's occur in a single n */
unsigned
popcount(unsigned n, unsigned d) {
int result = 0;
while (n != 0) {
result += ((n%10) == d);
n /= 10;
}
return result;
}
/* Compute how many d's occur all numbers from 1 to n */
unsigned
count(unsigned n, unsigned d) {
/* Special case single-digit n */
if (n < 10) return (d > 0 && n >= d);
/* If n does not end in 9, recurse until it does */
if ((n % 10) != 9) return popcount(n, d) + count(n-1, d);
return 10*count(n/10, d) + (n/10) + (d > 0);
}
该案例的丑陋n < 10
再次来自范围是 1 到n
而不是 0 到... 对于任何大于或等于 的n
单个数字,计数为 1,除非为 0。n
d
d
将此解决方案转换为非递归循环是 (a) 微不足道的,(b) 不必要的,并且 (c) 留给读者作为练习。
[更新 2]
最后(d > 0)
一项也来自 1 到n
而不是 0 到的范围n
。当n
以 9 结尾时,从 1 到n
包含 1 的数字有多少个有尾数d
?好吧,当d
为零时,答案是n/10
;当d
非零时,它比那多一,因为它包括值d
本身。
例如,如果n
是 19 并且d
是 0,则只有一个较小的数字以 0 结尾(即 10)。但如果n
是 19 和d
2,则有两个以 2 结尾的较小数字(即 2 和 12)。
感谢@Chan 在评论中指出这个错误;我已经在代码中修复了它。