题目详情
这里牛客网上的叙述及其的敷衍,还是力扣上的详细很多,输入的是正整数,因为n可以很大,所以要考虑时间复杂度,不能O(N)哦。
题解
1的个数
针对这题,首先要注意一点在于:题目是统计1的个数,而不是统计含1的数字数。所以11不是一次,而是两次,因为出现了两个1。这里需要特别的注意,否则很容易弄错。
需要找规律
对于一个数n,我们自然不能去遍历1-n中的每个数,去统计每个数的1的个数,进行累加,这样的时间复杂度太高,要超时。
所以我们需要一种有规律的方式来解决,来快速的知道,n它有多少个1
规律
我们研究一下0-9,发现这其中有1个1
10-19,这里有11个1(11有两个1),这里可以分成10-19在十位上有1个1,而11在个位上有一个1
20-29有1个1
···
后面的都是只有一个1
所以0-99内统计一下有20个1
然后同样的方式,我们来统计0-999(1000以内)有多少个1
我么分成000-099,100-199,···900-999
这样来计算,先统计十位、个位的1,就是20*10=200
而100-199是百位上有1,所以有100个1
故000-999中有200+100 = 300个1
然后再类推,10000以内(不包含10000本身)有4k个1,100000以内有5w个1
所以便可以总结出规律,100···00(level个0),则有unit = level*10^(level-1) 个1
如此,我们便获得了这种整的数中的1的个数的规律
不过这里我们暂时只考虑了1000···0这种情况
至于X00···00这种呢
其实也很好办,就是X00···000,当X>1时,一定是包含1xxxxxxx的,所以我们先算基本的个数
X*unit,然后再加上1开头的,1xxxxxxxx的个数就行,也就是10^level个1。总的加起来就ok
如此便是求很整的数的1的个数
对于一个不那么整的数的逻辑
比如9667这个数,应该如何计算呢?
逻辑如下
- 比如9667,9个k,那么首先是9*300+1k = 3700
- 然后是667,6个百,所以是6*20+100 = 220
- 然后是67,是6*1+10 = 16
- 然后是7,是1
- 结果是3700+220+16+1=3937
我们从高位到低位,一位一位的分别取处理
这里需要注意一点
当我高位为1时,则我所有的1的个数就是高位所含有的1的个数和低位的数值相加
例如1677这个数,
我需要计算1k以内的所有1的个数,
然后1000-1677的千位上1的个数,(重点)
最后才是1000-1677的百位,十位,个位的1的个数
代码
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
if(n<1) return 0;
int res = 0;
//进行拆分
while(n!=0){
int mod = n%((int)Math.pow(10,(int)Math.log10(n)));
String str = String.valueOf(n);
if(n>9&&str.charAt(0)=='1') res+=mod+1;//保证一定是十位数以上的才行
res += getOneNum(n-mod);
n = mod;
}
return res;
}
//获得整的数中的1的个数
private int getOneNum(int n){
if(n<10) return n>=1 ? 1 : 0;//个位数
//下面都是整的,10,200,3000这种
int level = 0;//统计级别
while(n>=10){
n /= 10;
level++;
}
//计算基本单位
int unit = level*(int)Math.pow(10,level-1);
int res = n * unit +(n>1?(int)Math.pow(10,level):0) ;
return res;
}
}