1-1统计数字问题(详解)

题目描述:从1页到n页,统计0到9出现的次数

题解一:暴力

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;
int main(){
    int c[10]={0};
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        int tmp=i;
        while(tmp){
            c[tmp%10]++;
            tmp/=10;
        }
    }
    for(int i=0;i<=9;i++){
        cout<<c[i]<<endl;
    }
}

题解二:递归

用递归方法显然比暴力节省了时间,解题思路可分为以下6步。

步骤一:求数n的位数len

公式log(n)+1;

可用cmath里的函数:double log10(double x)来求解

步骤二:划分区间并加和

把数n划分成区间,eg,

223,可划分成00-99,100到199(两个区间)

3333,可划分为000-999,1000到1999,2000到2999(三个区间)

在这每一个区间内(除了最高位)0-9出现的次数是(len-1)*10^(len-2)----(列举一下可发现规律

现在我们知道了每个区间的0-9出现的次数,该求区间数了:

如233区间数就是2,3333的区间数就是3,所以数n的区间数p是n/(10^(len-1))

综上,如3333,从000-2999(除了最高位)0到9的每个数的个数为p*(len-1)*10^(len-2)

步骤三:最高位加和

如3333,上一步我们已经算好从000到2999,(除了最高位)0到9每个数的和了

现在来算最高位

最高位(第4位)的数有0、1、2、3

0:0000-0999共1000个

1:1000-1999共1000个

2:2000-2999共1000个

3:3000-3333共333+1个

设最高位为m可得代码如下

for(int i=0;i<m;i++){
    c[i]+=pow(10.0,len);
}
c[m]+=1+n%((int)pow(10.0,len-1));

步骤四:递归

处理完3333的000-2999个数以及3作为第len位的情况剩下333递归处理333

即要处理t=n%(10^(len-1))

注意:

(1)若t为0,比如200,此时c[0]要加len-1也就是2!

若数10,则t为0,c[0]要加len-1,也就是1,

然后结束递归.

(2)若t不为0,这里特判一下,若lenT!=len-1说明是诸如10010这种情况,中间两个0还是要处理的,

c[0]+=(len-lenT-1)*(t+1)

步骤五:递归结束后处理0的情况

for(int i=0;i< len;i++) {
    c[0]-=(int)pow(10.0,(i));
}

这个公式可以这么理解:

当n=21时,要减去的0有:10^0(红色部分)+10^1(黄色部分)

当n=123时,要减去的0有:10^0(红色部分)+10^1(黄色部分)+10^2(灰色部分)

步骤六:输出

(ps,做的时候遇到了一个坑,pow(a,b)函数因为精度问题,有时会输出不准确的结果,所以,在前面加一个round函数,就准确了……我算的时候pow(10.0,len)len是2结果算出来是99……,这个问题还是需要注意的!)

代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>

using namespace std;

int c[10];

void solve(int n){
    //数n的位数
    int len=log10(n)+1;
    //最高位的值
    int p=n/((int)round(pow(10.0,len-1)));
    for(int i=0;i<10;i++){
        c[i]+=p*(len-1)*(int)round(pow(10.0,len-2));
    }
    for(int i=0;i<p;i++){
        c[i]+=(int)round(pow(10.0,len-1));
    }
    int t=(int)round(pow(10.0,len-1));
    t=n%t;
    if(t==0){//如果t为0
        c[p]++;//最高位加1
        c[0]+=len-1;//0位加len-1
        return ;
    }
    int lenT=log10(t)+1;
    if(lenT!=len-1){//若像10010这种情况,中间2个0也要相应的处理
        c[0]+=(len-lenT-1)*(t+1);
    }
    c[p]+=1+t;
    return solve(t);
}

int main(){
    int n;
    while(cin>>n){
        fill(c,c+10,0);
        int len=log10(n)+1;
        solve(n);

        for(int i=0;i<len;i++){
            c[0]-=(int)round(pow(10.0,i));
        }

        for(int i=0;i<10;i++){
            cout<<i<<" : "<<c[i]<<endl;
        }
    }
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值