题目描述:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。
解析:
1、将整数划分成2部分,以个位、十位、百位.....分别为分界线,以32459为例,以百位为分界位为例,可分为324和59两部分
int a = n/i,b = n%i; i = 100; //a为前一部分,b为后一部分
2、当分界位为0时,32059 左部分有(0~31)32种可能时,右边有10*10种可能,共有32*10*10种选择,
因为百位为1时,小于32059的数左边只能取(0~31),当左边为32时,右边部分不管取什么,百位都不能为1
if(a%10 == 0) {sumValue = sumValue + a/10*i;}
3、当分界位为1时,32159 左部分有(0~31)32种可能时,右边有10*10种可能,共有32*10*10种选择
左边为32时,右边部分有(0~59)60种选择
if(a%10 == 1) {sumValue = sumValue + (a)/10*i + b + 1;}
4、当分界位大于1时,32259百位可以取到1,左部分有(0~32)33种可能时,右边有10*10种可能,共有33*10*10种选择
为什么这个是33,以上两者是32,因为不管左部分取(0~31)还是32时,右边都有10*10种可能。
if(a%10 >= 2) {sumValue = sumValue + (a/10 + 1)*i;}
这三个条件语句可以融合成一条语句即:
代码为:
int NumberOf1Between1AndN_Solution(int n)
{
if(n < 0) return 0;
int sumValue = 0;
for(int i = 1; i <= n;i *= 10)
{
int a = n/i,b = n%i;
sumValue = sumValue + (a + 8)/10*i + (a%10 == 1)*(b + 1);
}
return sumValue;
}
为什么是(a+8)/10,这一句是以上三种条件下的融合,
根据以上三式可知,当分界位为0和1时,0和1加8除以10还是0,2~9加8除以10为1,(a/10 + 1)的效果,左边多一种可能。
还有一种方法,就是从1到n每个数都进行判断是否含有1,它的时间复杂度为O(n*logn),上种方法时间复杂度为O(logn),当n特别大时,快慢可想而知,但是这个方法容易想到,上一个方法难以想到。
int NumberOf1Between1AndN_Solution(int n)
{
if(n < 0) return 0;
int sumValue = 0;
for(int i = 1; i <= n; ++i)
{
sumValue += NumberOf1(i);
}
return sumValue;
}
int NumberOf1(int num){
int sum = 0;
while(num){
if(num%10 == 1){sum++;}
num = num/10;
}
return sum;
}