题目:输入一个整数n,求1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中1包含1的数字有1,10,11,和12,1一共出现了5次。
思路:(1)遍历解法,从1到n遍历每一个数,每个数判断1的个数,判断1的个数采取不断模10取余的方法,对于数字n,它有logn位,那么时间复杂度为O(nlogn),当n很大时,需要大量的计算,运算效率不高。
(2)如果我们能统计每个位上出现的次数,最后总次数就是把每个位上出现的次数加起来就可以了。例如输入数字21345,我们把数字分成两段,1~1345,1346~21345.先从高位计算1的个数,如果1出现在万位上,那么有10000~19999总共有10000种可能。需要注意的是如果最高位是1,譬如数字是11345,那么做高位的1只可能出现在10000~11345中,有1345+1中可能。接着我们再统计1出现在后四位中。我们把1346~21345再分成1346~11345和11346~21345两段。如果1出现在千位上,那么剩余三位可以取0~9中的任意数字,总共有10的3次方1000中可能。1出现在剩下的低位中情况完全类似,所以1出现在剩下的后四位中总共有4*1000*2 = 8000个。接着再统计1~1345中1出现的个数,由于1345只是我们开始的数字21345去掉了最高位,所以完全可以用递归的方法解决。递归的终止条件是只剩下一位数字。
有了思路,接下来可以开始写代码,作者这里用了字符串处理输入数字,很巧妙。第一个字符串可以处理大数,第二字符串可以很方便的实现对数字的每个位进行操作。
int NumberOf1Between1AndN(int n)
{
if(n < 0)
return 0;
char str[50];
sprintf(str, "%d", n);
return NumberOf1(str);
}
int NumberOf1(char* str)
{
int length = strlen(str);
int first = *str - '0';
if(length == 1 && first == 0)
return 0;
if(length == 1 && first > 0)
return 1;
int NumOfFirst = 0;
if(first == 1)
NumOfFirst = atoi(str + 1) + 1;
else if(first > 1)
NumOfFirst = PowerBase10(length - 1);
int NumOfOtherDigit = first * (length - 1) * PowerBase10(length - 2);
int NumRecursive = NumberOf1(str + 1);
return NumOfFirst+NumOfOtherDigit+NumRecursive;
}
int PowerBase10(unsigned int n)
{
int result = 1;
for(int i = 0; i < n; ++i)
result *= 10;
return result;
}