case1:思想:找规律
可以这么做:依次计算1->n这个n个数中每位(个位、十位、百位…)上为1的数的总数
然后再把这些结果累加起来就是我们最终想要的结果。
具体做法:可以将n拆分成三个部分来看
high:n这个数比cur位 高的位的部分所表示的值
cur:表示当前计算的位上的值
low:n这个数比cur位 低的位所表示的值
比如:n=5014
假设当前计算的位为十位:
则把n拆为三个部分来看:
high = 50
cur = 1
low = 4
分析过程:
假设我们对5014这个数字求解。
1->n中这n个数中:
(1)个位上为1数的个数:记高位为high=501,当前位为cur=4。
那么高位从0~500变化所产生的数中,每一个变化所产生的数中个位为1的数只出现1个,即(个位为1)这样的数字如5001,4991,4981…共501个;
高位是501时,因为当前位是4,所以此时对应个位为1的数只有1个即5011。
所以总共出现的次数为high1+1=502。
(2)十位1出现的个数:记高位high=50,当前位为cur=1,低位为low=4。
那么高位从0~ 49变化的过程中,每一个变化中1出现10次,即(高位10)~(高位19)这样的数字;
高位为50的时候,因为当前位是1,所以我们要看低位来决定出现的次数,因为低位为4,所以此时出现5次,即5010~5014这样的数字。
所以总共出现的次数为high10+4=504。
(3)百位1出现的个数:记高位high=5,当前位cur=0,低位为low=14。
那么高位从0~ 4的过程中,每一个变化1出现100次,即(高位100)~(高位199)这样的数字;
高位为5的时候,因为当前位为0,所以不存在出现1的可能性。
所以总共出现的次数为high100+0=500。
(4)千位1出现的次数:记高位high=0,当前位cur=5,低位low=014。
那么因为没有高位所以直接看当前位,因为当前位为5,所以1出现的次数为1000,即1000~1999这样的数字。
所以总共出现的次数为high1000+1000=1000。
综上,最终的结果将每个位置出现1的次数累加即可。
然后通过分析我们发现了一个规律:
可以根据n这个数cur位的取值情况来推出 1->n 这n个数中cur位为1的数的个数,推导公式为:
if(cur0)则从1->n这个n个数中这n个数中cur位为1的数的个数 = high*i(i表示的位数比如i表示个位则i = 1,i表示十位则i = 10以此类推)
else if(i1)则从1->n这个n个数中这n个数中i位为1的数的个数 = highi+low+1
else 则从1->n这个n个数中这n个数中i位为1的数的个数 = highi+i
【tips:】
1、如何精确求一个数的个为,十位,百位
令 i= 1,10,100,…10^n(表示位数,i=1表示个位,i=10表示十位…)
则 n/i 就表示仅保留n中i位及i位以上的数
2、由1我们可得可以这么拆分一个数n
high = n/(i*10)
cur = (n/i)%10
low = n - (n/i)*i
//case1
class Solution {
public int countDigitOne(int n) {
if(n<1)return 0;
if(n<10) return 1;
int res = 0;
//i用于表示位数,i=1表示个位,=10表示十位
//注意i要用long而不能用int,因为最多可以有31位,所以i最大可为10^31,超出int所能表示的范围;2^31-1
long i = 1;
//数n的第i位及i位以上的数不为0时
while(n/i!=0){
long high = n/(i*10);
long cur = (n/i)%10;
long low = n-(n/i)*i;
if(cur==0){
res+=high*i;
}else if(cur==1){
res+=high*i+low+1;
}else{
res+=high*i+i;
}
//统计下一位上的情况
i*=10;
}
return res;
}
}
case2 思想:计算每个数个、十、百…位上为1的个数
//:暴力法直接超时
class Solution {
public int countDigitOne(int n) {
if(n<1)return 0;
if(n<10) return 1;
int res = 0;
for(int i=n;i>0;i--){
int temp = i;
while(temp>0){
int rs = temp%10;
if(rs==1){
res++;
}
temp/=10;
}
}
return res;
}
}