I would suggest adapting this algorithm from base 2 to base 10:
Number of 1s in the two's complement binary representations of integers in a range
The resulting algorithm is O(log N).
The approach is to write a simple recursive function count(n)
that counts the zeroes from 1 to n
.
The key observation is that if N ends in 9, e.g.:
123456789
You can put the numbers from 0 to N into 10 equal-sized groups. Group 0 is the numbers ending in 0. Group 1 is the numbers ending in 1. Group 2 is the numbers ending in 2. And so on, all the way through group 9 which is all the numbers ending in 9.
Each group except group 0 contributes count(N/10)
zero digits to the total because none of them end in zero. Group 0 contributes count(N/10)
(which counts all digits but the last) plus N/10
(which counts the zeroes from the final digits).
Since we are going from 1 to N instead of 0 to N, this logic breaks down for single-digit N, so we just handle that as a special case.
[update]
What the heck, let's generalize and define count(n, d)
as how many times the digit d
appears among the numbers from 1 to 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);
}
The ugliness for the case n < 10
again comes from the range being 1 to n
instead of 0 to n
... For any single-digit n
greater than or equal to d
, the count is 1 except when d
is zero.
Converting this solution to a non-recursive loop is (a) trivial, (b) unnecessary, and (c) left as an exercise for the reader.
[Update 2]
The final (d > 0)
term also comes from the range being 1 to n
instead of 0 to n
. When n
ends in 9, how many numbers between 1 and n
inclusive have final digit d
? Well, when d
is zero, the answer is n/10
; when d
is non-zero, it is one more than that, since it includes the value d
itself.
For example, if n
is 19 and d
is 0, there is only one smaller number ending in 0 (i.e. 10). But if n
is 19 and d
is 2, there are two smaller numbers ending in 2 (i.e. 2 and 12).
Thanks to @Chan for pointing out this bug in the comments; I have fixed it in the code.