题目:输入一个整数N,输出从1到N所有整数中1的个数。例如,输入数10,输出为2。
解题思路一:遍历从1到N
的所有数字,求出每个数中1的个数
,然后累加
起来,就是最终的结果。
代码实现:
//求出每个数中1的个数
int NmberOfI(unsigned int n)
{
int number = 0;
while (n)
{
if (n % 10 == 1)
{
number++;
}
n /= 10;
}
return number;
}
//求1到N所有整数中1的个数
int NumberOfOne(unsigned int n)
{
int number = 0;
for (size_t i = 1; i <= n; i++)
{
number += NmberOfI(i);
}
return number;
}
对于上边的代码,假设每个数中有N个1,那么它的时间复杂度为O(n*n)。因为上述代码对于每个数都进行了除或者模运算,效率比较低。
解题思路二:以计算1-21345中的1的个数为例。
把这段数分为1-1345
和1346-21345
两段计算。
- 1.首先,我们先计算
10000-19999
这段中1的个数。分成两种情况,如果给的数超过20000
,存在10000-19999
这段,则1的个数为10000个
;另一种情况是所给的数没有超过20000
,比如说12345,那么在这段中1的个数则为2345+1=2346
。 - 2.第二步,我们计算
1346-21345
这段中要多少个1。因为10000-19999
的万位已经计算过了,那么就只剩下后边的四位了。因为这段既存在以1位万位的,也存在以2位万位的,所以我们可以把1346-21345
分成两段1346-11345
和11346-21345
。在每一段中,万位1的个数已经确定,剩下的四位,先确定一位为1,总共四种可能,剩下的三位取值在0-9
,所以总共1的个数为2*4*10^3=8000种
- 3.最后一步,是求
1-1345这段
,这段上边问题的子问题,是一个递归的问题。可以分成1-345
和346-1345
两端计算,显然这里可以用递归解决。
代码实现:
int PowerBase10(unsigned int n)
{
int result = 1;
for (unsigned int i = 0; i < n; i++)
{
result *= 10;
}
return result;
}
int NmberOfI_op(const char* str)
{
assert(str);
if (*str<'0' || *str>'9' || *str == '\0')
return 0;
int first = *str - '0';
int len = strlen(str);
if (len == 1 && first == 0)
return 0;
if (len == 1 && first > 0)
return 1;
//1.以21345为例,第一段10000-19999
int numfirstdigit = 0;
//第一个数大于2,则万位有10000个1
if (first > 1)
numfirstdigit = PowerBase10(len - 1);
//第一个数等于1,则万位有后边几位的数+1(12345万位位1有2346个)
else if (first == 1)
numfirstdigit = atoi(str + 1) + 1;
//2.1346-21345(剩下的4位,1的位置可以有四种情况,1固定好之后,其他位有0-9十种情况,既4*10^3)
//但是1346-21345应该分为两端1346-11345、11346-21345,所以是8*10^3个
int numotherdigit = first*(len - 1)*PowerBase10(len - 2);
//3.0-1035(和上述方法一样,采用递归解决)
int numrecursion = NmberOfI_op(str + 1);
//最后1的个数是前三步的和
return numfirstdigit + numotherdigit + numrecursion;
}
//将数字转换为字符串方便计算
int NumberOfOne_op(int n)
{
if (n <= 0)
return 0;
char str[50];
//将一些内容格式化输入到字符串中
sprintf(str, "%d", n);
return NmberOfI_op(str);
}