题目:
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
方法一:
看完题目以后我们能想到的最直观的解法就是累加1~n中每个整数1出现的次数,通过每一个数的对10求余数,来判断他个位是否为1,如果大于10则除以10后再判断。代码很简单,如下。
int Number_Of_Between_1_And_N(int n)
{
int number = 0;
for(int i = 1;i <= n;i++)
number = number + Number_Of_1(i);
return number;
}
int Number_Of_1(int n){
int number = 0;
while(n)
{
if(n%10 == 1)
number++;
n = n/10;
}
return number;
}
方法二:
在上述方法中,当我们输入的n非常大的时候,需要大量的计算,运算效率不高,接下里让我们看一看能否在数字上找到1存在的规律,为了方便,我们不妨取一个较大的数字如21345。
我们把1 ~ 21345的所有数字分成两段,一段是1 ~ 1345;另一段是1346 ~ 21345 。
我们先看1346 ~ 21345中1出现的次数,1出现分两种情况,首先分析1出现在最高位的情况(本例中是万位),在1346~21345的数字中,1出现在万位是在10000 ~ 19999这一万个数中,一共出现了10000(10的4次方)次。值得注意的是,并不是对所有的5位数而言在万位出现的次数都是10000次,对于万位是1的数字来说,1只出现在10000 ~ 12345 的万位,出现的次数不是10的四次方次,而是除去最高位加1次,也就是2345+1次。
接下来分析1出现在除最高位之外的其他4位数的情况中,1346~21345,由于最高位是2,我们把这段数字在分成两段,1346 ~ 11345,11346 ~ 21345,先来看前一段 1346 ~ 11345 ,由于 10001 ~ 11345的万位已经是1固定,我们可以忽略它看成是0001 ~ 1345,前面还剩下1346 ~ 10000,可以看成是0001 ~ 10000,4位数选择其中1位为1,其余三位可以在0 ~ 9这10个数字中任意选择,因此为4 * 10 * 10 * 10次,11346 ~ 21345同理。
至于1 ~ 1345中1出现的次数可以用递归求得,这也是我们为什么要把1~21345 分成 1 ~ 1345和1346 ~ 21345两段的原因,因为把21345的最高位去掉就变成了1345,便于我们采用递归的思路。
int Number_Of_Between_1_And_N(int n)
{
if(n<=0)
return 0;
char strN[50];
sprintf(strN,"%d",n);
return Number_Of_1(strN);
}
int Number_Of_1(const char *strN)
{
if(!strN || *strN<'0'||*strN>'9'||*strN == '\0')
return 0;
int first = *strN - '0';
int length = strlen(strN);
if(length == 1&& first == 0)
return 0;
if(length == 1&&first >1)
return 1;
//假设strN是“21345” numFirstDigit 为万位1的次数
int numFirstDigit = 0;
if(first>1)
numFirstDigit = PowerBase10(length-1);
else if(first == 1) //最高位为1
numFirstDigit = atoi(strN +1)+1;
//numOtherDigits 是1346~21345除最高位之外的数位中1的数目
int numOtherDigits = first *(length-1)*PowerBase10(length-2);
//numRecursive 是1~1345中的数目
int numRecursive = Number_Of_1(strN+1);
return numFirstDigit + numOtherDigits +numRecursive;
}
int PowerBase10(int n){
int result = 1;
for(int i=0;i<n;i++)
result = result*10;
return result;
}