思路1
最简单的方法,分别求从1到n之间每个数中的1的个数,由于整数n的位数为O(logn),我们要判断一个数有多少个1,需要判断其每一位是否为1,这样一个数就需要判断O(logn)次,而总共有n个数需要求,那么该方法的时间复杂度为O(nlogn)。
/*
* 累加1到n中每个整数1出现的次数。
* 每次通过对10求余数判断当前位是不是1.
*/
public int countNum(int n){
int number = 0;
for(int i = 1;i<= n;i++){
number+=numberOf1(i);
}
return number;
}
//直接返回当前数包含的1
public int numberOf1(int n){
int number =0;
while(n!=0){
if(n %10 == 1)
number++;
n = n/10;
}
return number;
}
思路2
按每一位来考虑,
1)此位大于1,这一位上1的个数有([n/10^(b+1]+1)*10^b
2)此位等于0,为([n/10^(b+1)])*10^b
3)此位等于1,在0的基础上加上n mod 10^b + 1
举个例子:30143
由于3>1
,则个位上出现1的次数为(3014+1)*1
由于4>1
,则十位上出现1的次数为(301+1)*10
由于1=1
,则百位上出现1次数为(30+0)*100+(43+1)
由于0<1
,则千位上出现1次数为(3+0)*1000
注:以百位为例,百位出现1为100~199
,*100
的意思为单步出现了100~199
,100次,*30
是因为出现了30次100~199
,+(43+1)
是因为最后一次301**
不完整导致。
整个过程的示意图如下:
需要注意一点:由于测试系统要求的输入数据最大为1,000,000,000,因此用int会溢出,要用long long,另外比较坑跌的一点是a可能比b大,居然都没有说明,有点坑了。
/*
* 根据数字规律分别统计从1到区间两端点出现1的个数 ,然后相减
* 比如count(a,b)=count(0,b)-count(0,a-1)
*/
public long countNum1(long num) {
if(num<=0) return 0;
long count = 0;//统计1出现的总次数
long current;//当前位的值
long base = 10;//当前位的基
long remain = 0;//当前位就是1时,当前位后面剩余的数,比如132,则remain=32
while(num>0) {
//当前位
current = num%10;
//统计当前位左边所有位可能的值,比如3012,当前位为2,则num=302
num = num/10;
//根据当前位的值确定当前位可能出现1的次数
if(current > 1) {
count += (num+1)*base;
}else if(current==1) {
count += (num*base + remain + 1);
}else {
count += num*base;
}
//下一位的求解可能需要用到不完整的部分值
remain += current*base;
base *= 10;
}
return count;
}
//求指定区间的1出现的个数
public long count(long a,long b) {
long result;
if(a>b) {
result = countNum1(a) - countNum1(b-1);
}else {
result = countNum1(b) - countNum1(a-1);
}
return result;
}