经典的数1问题

编程之美里有一道经典题目:从1~n的数字里一共出现过多少次数字“1”。
之前自以为看懂了结果在牛客网上做到一道类似的题完全过不去,就把这道题彻彻底底分析一下。

牛客网原题:
牛牛新买了一本算法书,算法书一共有n页,页码从1到n。牛牛于是想了一个算法题目:在这本算法书页码中0~9每个数字分别出现了多少次?

输入描述:
输入包括一个整数n(1 ≤ n ≤ 1,000,000,000)

输出描述:
输出包括一行10个整数,即0~9这些数字在页码中出现的次数,以空格分隔。行末无空格。

输入例子:
999

输出例子:
189 300 300 300 300 300 300 300 300 300

这题的思路的概括是:按位数某个数字出现过多少次。也就是说,固定某一位上的某个数,看看此时有几种情况。

举个栗子:
对统计1~785012中5出现的个数
我们先考虑5在低位第三位出现的次数,我们就将第三位固定为5:XXX5XX。
此时,问题就转化为,“X”处一共有多少种组合方式
高位的3位可以从000到785之间,低位的2位可以从00到99之间。
但是,当高3位为785时,7855XX无论如何都超过了785012,因此,高三位可以选择的只有000~784(共785个数字),高三位和低二位一共有785*100种组合方式。

我们再考虑5在低位第四位出现的次数,我们就将第四位固定为5:XX5XXX。
与前面相同,高位的二位可以从00到78之间,低位的三位可以从000到999之间。
当高位是00到77时,与前面一种情况是相同的,一共可以组合出78*1000种情况。
当高位是78时,785XXX就有超过785012的可能了,后三位的选择局限在000到012之间,共13中情况。
两者结合就是:78*1000+13

最后考虑5在低位第5位出现的次数,我们就将第五位固定为5:X5XXXX。
与前面相同,最高位可以从0取到7,低四位可以从0000取到9999.
当最高位为0到6时,与第一种情况相同,一共可以组合出7*10000中情况。
当高位是7时,与前面的情况不同的是,75XXXX是不会越界的,后四位可以从0000取到9999,所以可以组合出10000中情况。
两者结合就是:7*10000+10000

设我们讨论的当前位的值为A,我们统计的数字为B,我们前面讨论的三种情况,其实分别是:A小于B时,A等于B时,A大于B时。

上述的计算方式对于1~9是通用的,可以写成这样的函数:

long int cal(long long int page, int data)
{
    long long int num = 0;
    long long int temp = 1;
    long long int formal = page;
    while (page != 0)
    {
        if (page % 10 < data) num += page / 10 * temp;
        else if (page % 10 == data) num += page / 10 * temp + (formal%temp + 1);
        else num += (page / 10 + 1) * temp;
        page /= 10;
        temp *= 10;
    }
    return num;
}

但是,当统计0的个数时,情况又有所不同。还用785012讨论,如果我们要统计低位低2位出现0的次数,按照前面的方法,我们可能把00000X讨论进来,认为这也是第二位出现0的情况,但是这个数只能表示为X,它的第二位根本不存在。
因此,针对0这种情况,我们需要单独讨论,只需要分成两类,不可能存在当前位小于0的情况,不过每次高位的取值要把全0的情况剔除
代码可以这样写:

long int cal0(long long int page)
{
    long long int num = 0;
    long long int temp = 1;
    long long int formal = page;
    while (page != 0)
    {
        if (page % 10 == 0) num += (page / 10 - 1) * temp + (formal%temp + 1);
        else num += (page / 10) * temp;
        page /= 10;
        temp *= 10;
    }
    return num;
}

牛客网那题完整的解题代码是:

#include<iostream>
using namespace std;

long int cal(long long int page, int data)
{
    long long int num = 0;
    long long int temp = 1;
    long long int formal = page;
    while (page != 0)
    {
        if (page % 10 < data) num += page / 10 * temp;
        else if (page % 10 == data) num += page / 10 * temp + (formal%temp + 1);
        else num += (page / 10 + 1) * temp;
        page /= 10;
        temp *= 10;
    }
    return num;
}

long int cal0(long long int page)
{
    long long int num = 0;
    long long int temp = 1;
    long long int formal = page;
    while (page != 0)
    {
        if (page % 10 == 0) num += (page / 10 - 1) * temp + (formal%temp + 1);
        else num += (page / 10) * temp;
        page /= 10;
        temp *= 10;
    }
    return num;
}



int main()
{
    long long int formal;
    cin >> formal;
    long long int num = cal0(formal);
    cout << cal0(formal);
    for (int i = 1; i <= 9; ++i)
        cout << " " << cal(formal, i);
    getchar(); getchar();
    return 0;
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值