题目描述
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
设计思想一:
最容易想到的:每次对10求余,判断个数是否为1,再将原数除以10作为下次除以10求余的输入
int NumberOfBetweenAndN(unsigned int n)
{
int number=0;
for(unsigned int i=1;i<=n;++i)
number+=NumberOf1(i);
retrun number;
}
int NumberOF1(unsigned int n)
{
int number=0;
while(n)
{
if(n%10==1)
number++;
n=n/10;
}
return number;
}
法一的时间复杂度分析:对每个数字n,有O(logn)位;从1~n有n个数,所以总的时间复杂度O(nlogn),如果n很大,会很耗时。
法二:
先看最高位出现1的次数:
以21345为例,万位的的1出现在10000~19999,共10^4个,但是如果n的最高位不是1的话,比如12345,万位的1只能出现在10000~12345,共2345+1个。所以最高位是1的数,要分输入数字n的最高位是大于1,等于1,还是小于1来计算:大于1 时候,假设最高位是first,则最高位是1的数字个数NumOfFirst1=10^(n的位数-1);若first==1,则NumOfFirst1=(n-10000)+1
再分析次高位至最低位:
仍以21345为例,若是5位数,考虑低四位,在1346~11345和11346~21345这两段,每一段除万位外,1的出现次数是4*10^3=4000(选定其中1位是1,另外3位可任选,就是一个排列组合),两段就是8000
接下来考虑1~1345,可以用递归,这也是拆出1346~11345和11346~21345这两段的原因
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if(n<=0)
return 0;
char strN[50];
sprintf(strN,"%d",n);
return NumberOf1(strN);
}
int NumberOf1(const char* strN)
{
if(!strN||*strN<'0'||*strN>'9'||*strN=='\0')
return 0;
int first=*strN-'0';
unsigned int length=(unsigned int)strlen(strN);
if(length==1&&first>=1)
return 1;
if(length==1&&first==0)
return 0;
//当n位数不止1位时候,以21345为例,计算最高位是1 的数的个数
int numberOfFirst=0;
//if(first==1)
// numberOfFirst=atoi(strN+1)+1;
// else if(first>1)
// numberOfFirst=PowerBased10(length-1);
if(first>1)
numberOfFirst=PowerBased10(length-1);
else if(first==1)
numberOfFirst=atoi(strN+1)+1;
int numOfOtherDigits=first*(length-1)*PowerBased10(length-2);
//最高位是几就分几段,每段除最高位外,剩length-1个位置可以放1,length-1位中某一位放1后
//其余length-2每位有0~9 10种放法
int NumOfRest=NumberOf1(strN+1);
return numberOfFirst+numOfOtherDigits+NumOfRest;
}
int PowerBased10(unsigned int n)
{
int res=1;
while(n)
{
res*=10;
n--;
}
return res;
}
};
关于本文用到的sprintf()的用法:https://blog.csdn.net/qq_34793133/article/details/81261178