【读书笔记】《编程之美》
【读书笔记】《编程之美》
1的数目
题目:给定一个十进制的正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有“1”的个数
这一道题有两种解法。
解法1
暴力解法
分析
题目中给了N,那么需要计算的区间范围就是[1,N],我们可以进行遍历这个区间,然后处理区间的每一个数字,并记录下1出现的次数。
代码
public static long count(long n){
long cnt = 0;//记录1出现的次数
for (int i=1;i<=n;i++){//遍历区间[1,n]
int j = i;
while (j!=0){//循环遍历j的每一位数
if (j%10==1){//如果当前位是1,cnt++;
++cnt;
}
j/=10;//进入下一位判断
}
}
return cnt;
}
在博主的电脑上当前n=500000000,计算出结果需要6871ms。
解法2
数学找规律求解
无疑,一个数字是由个位,十位,百位......构成,
1,2,3,4,5,6,7,8,9,10,11,12,13......,n
先数上面个位数的1,然后数十位的1,然后数百位的1,然后数......
答案就出来了。
所以是否可以使用 个位上1出现的次数 加上 十位上出现1的个数 加上 百位上出现1的个数 .......
那么我们就看看能否求出个位,十位,百位......上的1的个数吧
可以求出来,那么自然这就是一种解法。
个位数1的出现次数
切记下面只统计个位的1,其他位不统计。
数字有一位
-
假设当前n为4
那么出现1的次数有1次(1),
数字有两位
-
假设当前n为14
那么出现1的次数有2(1,11,)
-
如果n为24呢?
出现的次数为3(1,11,21),易发现1+2(十位上的数)=3,
因为14,24都是在遍历十位上的数
-
如果当前n为20呢?
出现次数为2(1,11),没有21了,所以我们需要判断个位数是否大于等于1。
-
如果数字有两位,n=ba
情况 结果 个位数等于0 1 + b - 1 = b 个位数大于等于1 1 + b
数字有三位
-
假设当前n为114
那么出现1的次数12(1,11,21,...91,101,111),
其中(11,21,...,91,101,111)可以发现百位十位是从1-11,
-
如果n为124呢?
那么相对于上面多了一个121,也就是13次,
其中(11,21,...,91,101,111,121)可以发现百位十位是从1-12。然后再加上1即可。
可以发现出现的次数为个位数左边的数+1,即次数=n/10+1;
我们可以发现这个公式在数字有两位时,且个位数不等于0的时候同样适用。
可以发现一位数的时候同样适用。(题目:N(n)的范围为正整数,n即N)
-
如果n为130呢?
那么出现13次,和上面n为124结果一样,,即次数=n/10,
我们发现,这个公式在数字有两位时,且个位数为0的时候同样适用
个位通用公式
那么数字位数<=3时,个位数出现1的次数,N(n)属于正整数。
假设n=cba,位不存在用0表示。
情况 | 结果 |
---|---|
当个位数等于0 | cba/10=cb |
当个位数大于等于1 | 1 + cba/10=cb+1 |
我们发现者个公式兼容了一位,两位的情况,
假设当前n为4位数?发现表格中的公式仍然适用。
所以求个位出现1的次数,无论有几位数,都可以使用这个公式。
十位数出现1的次数
切记:下面只统计十位数的1,其他位数不统计。
数字有一位
-
假设当前n为4
那么出现1的次数有0次
数字有两位
-
假设当前n为14
那么出现1的次数有5(10,11,12,13,14),次数=个位数+1
-
如果n为24呢?
出现的次数为10(10,11,12,....,19)
-
如果当前n为35呢?
发现出现的情况和24一样,我们发现n如果为99,出现的情况和上面也一样,10-19已经是所有百位的1了。
易发现一下规律
如果n=ba
情况 结果 十位数等于0 即 b=0 0 十位数等于1 即 b=1 个位数 + 1= a + 1 十位数大于1 即 b>1 10 -
数字有三位
-
假设当前n为114
那么出现1的次数15(1-99,100,101,102,..,109,110,111,112,113,114),
1-99:按照数字有两位求,结果为10
110,...114:结果位5
故总次数为15,
-
如果n为124呢?
出现20次,(10-19,110-119)
-
如果n为130呢?
那么出现还是20次,和上面n为124结果一样,可以发现1都是出现在10-19(110-119)这个区间中,
10-19这个区间出现的次数即为答案。
每100中都会出现一次,10-19区间中十位出现1的次数为10次。
114=100+14
假设n=abc
[1,abc]=[1,a*100]+[a*100+1,abc]
[1,a*100]:那么其中有a个100,1的次数为10*a。
[a*100+1,abc]:中十位出现1的次数与十位是否为0,1有关,结合数字有两位的公式
若b=0,次数为0,
若b=1,次数为c+1
若b>1,次数为10
可以发现十位出现的次数与a,b,c的取值都有关
-
十位通用公式
那么数字位数为三位时,十位数出现1的次数,N(n)属于正整数。
n=cba
情况 | 结果 |
---|---|
b=0 | 10*c |
b=1 | 10*c+a+1 |
b>1 | 10*(c+1) |
我们发现数字为一位,两位情况被数字为三位的情况所兼容。
当n为四位数时,仍然成立.
所以求十位出现1的次数,无论有几位数,都可以使用这个公式。
百位出现1的次数
切记:下面只统计百位的1,其他位不统计。
数字有一位
次数为0
数字有两位
次数为0
数字有三位
-
假设当前n为123
那么出现1的次数为24(100,101,...,123),23+1
-
假设当前n为159
那么出现1的次数为60次,59+1
我们易发现出现次数与百位以下(更低位)有关,低位+1为结果
数字有四位
-
假设当前n为1123
那么出现1的次数为124(100-199,1100-1123)
即每次1000中都会有100个,既和更高位有关,也和更低位有关
百位通用公式
n=dcba
情况 | 结果 |
---|---|
c=0 | d*100 |
c=1 | d*100+ba+1 |
c>1 | (d+1)*100 |
我们发现公式也兼容三位的情况。
当n为五位数时,仍然成立.
所以求百位出现1的次数,无论有几位数,都可以使用这个公式。
综合
我把上面三个通用公式表格都拿下来进行分析,且n=dcba,位不存在使用0表示
个位:(例:dcb不是相乘关系,不存在使用0表示即可)
情况 | 结果 |
---|---|
当个位数等于0 | dcba/10=dcb |
当个位数大于等于1 | 1 + dcba/10=dcb+1 |
十位:
情况 | 结果 |
---|---|
b=0 | 10*dc [10*更高位] |
b=1 | 10*dc+a+1 [10*更高位+更低位+1] |
b>1 | 10*(dc+1) [10*(更高位+1)] |
百位:
情况 | 结果 |
---|---|
c=0 | 100*d [100*更高位] |
c=1 | 100*d+ba+1 [100*更高位+更低位+1] |
c>1 | 100*(d+1) [100*(更高位+1)] |
- 我们发现百位乘100,十位乘10,个位乘1(略),
- 我们发现十位与百位的情况如此相似
我们将个位重新等价变形一下,
个位:
情况 | 结果 |
---|---|
a=0 | dcba/10=dcb [1*更高位] |
a=1 | 1 + dcba/10=dcb+1 [1*更高位+更低位+1] |
a>1 | 1 + dcba/10=dcb+1 [1*(更高位+1)] |
发现个位,十位,百位都是同样的公式,该公式具有普遍性,千位也是......
按照公式可以写出如下代码。
代码
综上可以发现:求当前位出现1的次数,只需要知道当前是第几位,更高位,更低位即可求出当前位出现1的次数。
private static long count(long n) {
long count = 0; //出现1的次数
long factor = 1; //标记当前是个位还是十位还是百位等,就是表格中的1,10,100数字
long lowernum = 0; //更低位
long currnum = 0; //当前位
long hightnum = 0; //更高位
//循环遍历求出每一位
while (n / factor != 0) {
lowernum = n - (n / factor) * factor;//求出更低位
currnum = (n / factor) % 10;//求出当前位
hightnum = n / (factor * 10); //求出更高位
switch ((int) currnum) { //判断当前位
case 0://当前位为0,出现次数,当前[个,百,千....]位*更高位
count += hightnum * factor;
break;
case 1://当前位为1,出现次数,当前[个,百,千....]位*更高位+更低位+1
count += hightnum * factor + lowernum + 1;
break;
default://当前位大于1,出现次数,当前[个,百,千....]位*(更高位+1)
count += (hightnum + 1) * factor;
break;
}
//处理下一位
factor *= 10;
}
return count;
}
在博主的电脑上当前n=500000000,计算出结果需要0.2911ms。
相比较两个程序的执行时间,执行速度提高了30000倍左右。