private static int countOnes(int n) {
int ones = 0;
for(int m = 1; m <= n; m *= 10) {
int a = n/m, b = n%m;
ones += (a+8)/10*m + (a%10 == 1 ? b+1 : 0);
}
return ones;
}
思路:将n按位考虑,从个位到最高位,计算每一位上的1各出现几次,然后相加。
时间复杂度:O(log n)
举例:
m | a | b | ones加数因子 |
---|---|---|---|
1 | 111 | 0 | 11+1 |
10 | 11 | 1 | 10+2 |
100 | 1 | 11 | 0+12 |
ones总共为36
拓展:从1到n的所有整数的十进制表示中一共有多少个k(k=1,2,...,9)
private static int countKs(int n, int k) {
int ks = 0;
for(int m = 1; m <= n; m *= 10) {
int a = n/m, b = n%m;
ks += (a+9-k)/10*m + (a%10 == k ? b+1 : 0);
}
return ks;
}
时间复杂度:O(log n)
k=0
这个情况比较复杂,需要分情况讨论
private static int countZerosDirectly(int n) {
int zeros = 0;
for(int m = 1; m <= n/10; m *= 10) {//最高位不用考虑
int a = n/m, b = n%m;
if(m > 1 && a%10 == 0)//表示n的高位(非个位)含0的情况,此时a/10不能直接乘乘数因子m,否则会多算
zeros += (a/10-1)*m + (b+1);
else//其他情况正常计算,此时k=0的情况是比非0情况容易的
zeros += a/10*m;
}
return zeros;
}
时间复杂度:O(log n)
对于k=0的情况,正向思维不容易直接想出以上两种情况,这里也可以逆向思维,算出1到n这n个整数中0到9这10个数字总共出现多少次,减去1到9出现的总次数,就得到0出现的次数了(也可以用于验证正向思维结果)。
private static int countZerosIndirectly(int n) {
int zeros = countN(n);
for(int i = 1; i < 10; i++) {
zeros -= countKs(n, i);
return zeros;
}
private static int countN(int n) {
int count = 0;
int m = 1, k = 1;
for(m = 1; m <= n/10; m *= 10) {
count += 9*m*k;
k++;
}
count += (n-m+1)*k;
return count;
}
时间复杂度:O(log n),但显然代价大于直接法。