题目要求:
查找出小于等于n的非负数中出现1的个数
找到的规律:
1. 9999…9999 n个9 中1的个数为countn9 = n*pow(10,n-1)
2. x9999…9999 n个9 中1的个数为countxn9 = (x+1)*n*pow(10,n-1) + pow(10,n)
3. 1241343210000000000—-124134321x999999999这个区间的一的个数
- 在前面的1的个数 = pow(10,9的个数)*(x+1)
- 后面1的个数 = 根据x是否等于判断使用第1条或者第2条公式
- 1的个数就是 = 前面1的个数 + 后面1的个数
通过分段把所有区间的1的个数求出来累加起来得到结果
测试时候输入的整数为2147483647,发现会溢出,代码可以ac
例如:
2415
可以划分区间为:
区间 | 1的个数 | 前段长度 | 后段长度 |
---|---|---|---|
0000-1999 | 1000+300*2=1600 | 0 | 4 |
2000-2399 | 100+20*4=180 | 1 | 3 |
2400-2409 | 1 | 2 | 2 |
2410-2415 | 1*6+1=7 | 3 | 1 |
合计 | 1600+180+1+7=1788 |
/*
* 99...9 n个9 n*pow(10,n-1)
* x99...9 x后面n个9 x非0 n*pow(10,n-1)*(x+1) + pow(10,n)
*/
public int countDigitOne(int n) {
//小于等于0的数字特判
if(n<=0) return 0;
//求出位数并把每一位都保存在list中,是逆序的
List<Integer> digits= new ArrayList<Integer>();
int flag = 0;
while(n!=0) {
digits.add(n%10);
if(n%10==0) flag=1;
n/=10;
}
int length = digits.size();
int i = length;
//记录结果
int count = 0;
//分的区间 和 位数相同
for(int j=1;j<=length;j++) {
//最后一个特判
if(j!=length) {
//0跳过
if(digits.get(length-j)==0) {
continue;
}
//非0处理
int count1=0;//记录前段1出现的个数
int count9=length-j;//记录后端9出现的个数
int now_num=digits.get(length-j)-1;//后段x
//计算1的个数
int i2 = length;
for(int k=1;k<=j-1;k++) {//从第二次区间划分才记录
if(digits.get(--i2)==1) {
count1++;
}
}
//计算后段1的个数
if(now_num!=0) {
count = count +
(now_num+1)*(int)Math.pow(10,count9-1)*count9 +
(int)Math.pow(10,count9);
}
else {
count = count + (int)Math.pow(10,count9-1)*count9;
}
//前面有1 需要计算前段1的个数
int temp2 = 1;
temp2 = count1*(now_num+1)*(int)Math.pow(10,count9);
count += temp2;
}//最后一个特判
else {
//记录一下前段1的个数
int count1=0;
int i2 = length;
for(int k=1;k<=j-1;k++) {//从第二次才有
if(digits.get(--i2)==1) {
count1++;
}
}
//算出前段1的个数
count += (count1 * (digits.get(length-j)+1));
//后段只要不是0 就有1个
if(digits.get(length-j)!=0)
count++;
}
}
return count;
}