在csdn上面看到一道题目:
对于任意正整数n,f(n)表示从1到n中数字1出现的次数。例如f(3)=1; f(11)=4; f(13)=6;
设计一个算法来计算f(n);
最开始想到的算法是最直接的算法:对1到n的每个数字m,将m转换成字符串,计算该字符串中字符‘1’出现的次数。所有的次数的和就是计算的结果。具体算法如下:
public long countAppearTimes(long n)
{
long appearTimes = 0;
for (long i = 1; i <= n; i++)
{
String strN = String.valueOf(i);
int length = strN.length();
for (int i=0; i< length; i++)
{
if (strN.charAt(i) == ‘1’)
{
appearTimes = appearTimes + 1;
}
}
}
return appearTimes;
}
这个算法比较直接,也比较好理解,但是效率太低下。当n比较小的时候还显示不出来,但是当n比较大的时候,就十分明显了。在我的机器上,当n=999999999时,运行了540047ms,也就是9分钟的时间。
是否有比较简单、高效的算法呢?
我们来做一下转换。假设给出的n是一个m位的数字,可以表示为
a1a2a3a4a5a6......am。那么将从1到n的所有的数字都按m位编写,不足的在左端补0。具体形式如下
0 0 0 0 ...... 0 0 1
0 0 0 0 ...... 0 0 2
......
0 0 0 0 ...... 0 1 0
0 0 0 0 ...... 0 1 1
......
a1 a2 a3 a4 。。。。。。 am-2 am-1 am
我们可以发现以下的规律:
个位上的数字,每10个中就有1个1;
十位上的数字,每100个中有10个1;(10——19)
百位上的数字,每1000个中有100个1;(100——199)
依次类推,在从前向后数的第i位上的数字,每10m-i+1个中就有10m-i个1。
因此,对于n= a1a2a3a4a5a6......am;第i位的数字为ai
1. 如果ai 等于0,那么第i位上有 a1a2a3a4a5a6...... ai-1 * 10 m-i 个1。
也就是说,如果ai 等于0,那么第i位上的1的个数为n/10m-i+1*10m-i个1;
注意,/表示整除,例如132/10 = 13。以下相同。
例如:n=13059;第三位为0。那么第三位上的1共有
13059/105-3+1*105-3=1300个。即
100——199,1100——1199,2100——2199,3100——3199,4100——4199,5100——5199,6100——6199,7100——7199,8100——8199,......,13100——13199。
2. 如果ai等于1,那么从1到a1a2a3a4a5a6... ai-1099...9,第i位上的1的个数可以根据情况1计算出来。从a1a2a3a4a5a6... ai-1100...0到a1a2a3a4a5a6... ai-11 ai+1 ai+2... am 中第i位上的1的个数为 ai+1 ai+2... am+1个,即n%10m-i + 1个。(%表示取余,例如132 %10 = 2。以下相同)所以第i位上共有n/10m-i+1*10m-i + n%10m-i + 1个1。
3. 如果ai位是其他的数字,那么第i位上共有n/10m-i+1*10m-i + 10m-i =(n/10m-i+1+1)* 10m-i个1。具体的推算各位可以自己去验证。
根据这思想,就有以下的算法。
public long countAppearTimes(long n){
long times = 0;
String str = String.valueOf(n);
int length = str.length();
for (int i=0; i<length; i++){
//注意这里i是从0开始的,上面的算法描述中的是从1开始的,因此以下的程序稍有不同。
long exp = Math.round(Math.pow(10, length-i-1));
long mod = n % exp;
long div = n / exp;
long tempTimes = 0;
if (str.charAt(i)=='1'){
tempTimes = mod + 1;
tempTimes = tempTimes + (div -1) * exp / 10;
times = times + tempTimes;
}
else{
if (str.charAt(i) == '0')
tempTimes = tempTimes + div / 10 * exp;
else
tempTimes = tempTimes + (div /10 +1)*exp;
times = times + tempTimes;
}
}
return times;
}
通过验证,这个算法和最原始的算法的结果是完全相同的(验证了从1到99999999)。
这个算法的效率如何呢?当n=999999999时,运行了109ms。
256

被折叠的 条评论
为什么被折叠?



