题目描述: 输入 一个整数,求 1~n 这 n 个整数的十进制表示中1 出现的次数。
例如输入 12 那么 1~12 这些整数中包含 1 的数字有 1 10 11 12 ,一共出现了 5 次
思路:
最简单的思路:从1 开始遍历到 n ,统计所有数字出现 1 次数的总和。如何统计一个数字出现 1 的次数?
我们可以每次对该数模 10 ,比较余数是否等于1 ,然后将该数除以10 。统计一个数出现几次 1 的时间复杂度为logn,
所有总复杂度为 nlogn.
具体代码:
//时间复杂度为 nlogn
public static int numberOfKBetween1AndN(int n,int k){
int num = 0;
for(int i = 1; i <= n; i++){
num += numberOf1(i,k);
}
return num;
}
private static int numberOf1(int n,int k) {
// TODO Auto-generated method stub
int num = 0;
while(n > 0){
if(n%10 == k)
num++;
n = n/10;
}
return num;
}
但是时间复杂度为 nlogn ,任然不是最好的。
我们可以根据数字规律来做道题,时间复杂度只需要 logn 。
一个数字(0 除外)可以出现在个位、十位、百位、千位......我们可以统计出它在各个位出现的次数。
以一个实际的例子来找出它的数字规律,比如现在输入一个数 15292。那么1~15292 中国 2 出现的次数是6653.
我们首先看个位,将个位的 2 去掉,变成了 1529,从 1 ~ 1529,一共有1529个数,所以此时出现1529次2,又因为
个位刚好为 2 ,所以应该再加上 1 ,即个位上出现 2 的次数为 1530.
接下来看 十位,1529去掉 9 变成 152 ,所以出现 1520次(因为还有个位,所以应该将152乘以 10),又因为 9 > 2 ,所以出现的次数为 1520 + 10 = 1530 次。
接着看 百位,把 2 去掉,变成 15,所以出现的次数为 1500次( 有个位和十位,所以要乘以100),此时 百位刚好为2,情况会比前面复杂点。我们可以简单分析:1~15200,百位出现 2 的次数为 1500次,15200~15292 百位出现 2 的次数为92+1 = 93 次,所以一共是 1593次。
接着是千位: 15去掉 5变成 1 ,所以在千位出现的次数是 1000,因为 5 > 2 ,所以应该再加上1000(因为前面有个、十、百位),所以有 2000 次。
接下来是 万位,因为万位是 1 ,小于2, 所以万位不可能出现 2 。
统计下来一共为 6653次。
接下来需要将上述分析过程转化为 代码
//根据数字规律,时间复杂度为 logn 的算法
public static int numOfN(int n,int x){
int count = 0;
int k = 1;
int temp = n;
for(int i = 1; i <= n; i *= 10){
k = temp / 10;
count += k*i;
int p = temp%10;
if(p > x )
count = count + i;
if(p == x ){
if(i > 1){
int b = n%i;
count += b + 1;
}
if(i == 1)
count++;
}
temp = k;
}
return count;
}
综合以上联众方法进行实际测试: