题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。
例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,1一共出现了5次。
最简单思路,就是从1-n一个刚数字进行判断然后计数即可,
int NumberOf1BeforeBetween1AndN_Solution1(unsigned int n)
{
int number = 0;
// Find the number of 1 in each integer between 1 and n
for(unsigned int i = 1; i <= n; ++ i)
number += NumberOf1(i);
return number;
}
int NumberOf1(unsigned int n)
{
int number = 0;
while(n)
{
if(n % 10 == 1)
number ++;
n = n / 10;
}
return number;
}
这个思路有一个非常明显的缺点就是每个数字都要计算1在该数字中出现的次数,因此时间复杂度是O(n)。当输入的n非常大的时候,需要大量的计算,运算效率很低。这里引入新的思路,以n=21345为例,可以分成两部分1-1345和1346-21345,先分析1236-21345这一部分20000个数字,1-1345迭代即可。
对于1236-21345分析1出现的次数,可以分两种情况:
a 1在最高位(万),一共在10000-19999中出现过,一共10^4次,当然这是在这个最高位数字2大于1的情况下,如 最高位原数字就是等于1,如11345则此时统计最高位1出现次数很明显就只有1345+1次
b 1在其他为(后四位),选取一位为1,有len-1=4种选法,每种指定1后,剩下3位有10^3种情况,再考虑最高位后1出现此时最高位数字2×4×10^3。
剩下的对1-1345类似分析即可。
代码:
int powerbase10(int n){
int res=1;
for(int i=0;i<n;i++)
res*=10;
return res;
}//10^n
int numberof1(string str){
if(str.size()<1)return 0;
int first=str[0]-'0';
unsigned int len=static_cast<unsigned int>(str.size());
if(len==1&&first==0)return 0;
if(len==1&&first>0){return 1;
}
int num_first=0;
int num_other=first*(len-1)*powerbase10(len-2);
int numrecursive=numberof1(str.substr(1));
if(first>1)num_first=powerbase10(len-1);
else if(first==1)num_first=atoi(str.substr(1).c_str())+1;
return num_first+num_other+numrecursive;
}