输入任意正整数n,统计1到n中1出现的次数,比如输入12,其中1,10,11,12出现了5次。
很容易会想到的方法是遍历1~n,然后逐位计算1的个数。这是同学写的一个代码。
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
void main(){
int n,count = 0;
cin>>n;
for(int i = 1;i <= n;i++){
string nStr = to_string(i);
int pos = nStr.find_first_of('1');
if( pos != string::npos) {
count++;
for(;nStr.find_first_of('1',pos + 1) != string::npos; count++, pos = nStr.find_first_of('1', pos + 1));
}
}
cout<<count<<endl;
}
很明显这种算法的时间复杂度为O(n)。但如果我们把n拆开逐位看待的话,就会发现其实1出现的次数是有规律的,而且很大部分是取决于这一位前面的数值,比如1234,对个位来说,遍历的时候每10次个位就会出现一次,因而个位起码出现了123(个位前面的数值)*10次,再加上个位数4比1大,再加上一次,个位一共出现1231次1;同理,对十位来说,每遍历100,十位就会出现10次1,因而十位至少出现12*10次1,再加上十位和个位上的数31比19大,因而再加上10~19这10次,总共出现130次(若十位为1则需要个位的数值,比如十位个位为12则记出现3次,同理可得);其他位也是同样的情况。这种算法需要循环的次数仅为n的位数长度,时间复杂度为O(1)。参考代码如下。
#include <iostream>
using namespace std;
void main()
{
int n,length=0,sum=0;
cin>>n;
int number=n;
while(number>0)//计算n的长度
{
number/=10;
length++;
}
for(int i=1;i<=length;i++)//从个位开始计算每个位上面1出现的次数
{
sum+=(int)(n/pow(10,i))*pow(10,i-1);//如1326,对个位(先不考虑个位情况下)出现1共132次,对十位(先不考虑十位情况下)1出现13*10=130次
int tmp=(int)(n/pow(10,i-1))%10;//取出本位上的数字,如1326,个位为6,十位为2等
if(tmp>1)
sum+=pow(10,i-1);//若此位大于1,则此位为1时所有情况都需要考虑上
if(tmp==1)
sum+=n%(int)(pow(10,i-1))+1;//若此位等于1情况,如1112,对十位,需要加上10,11,12,三种十位为1的情况,对百位加上100~112这13种百位为1的情况
}
cout<<sum<<endl;
}
比较两种算法的运行时间如下:
明显第二种算法时间少很多。
同时,这种方法也可以统计多位数的出现情况,如统计‘23’出现的次数就考虑每100出现一次‘23’即可。