【基础算法】计数问题

30 阅读5分钟

🌹作者:云小逸
📝个人主页:云小逸的主页
📝Github:云小逸的Github
🤟motto:要敢于一个人默默的面对自己,==强大自己才是核心==。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。==希望春天来之前,我们一起面朝大海,春暖花开!==🤟 👏专栏:C++👏 👏专栏:Java语言👏👏专栏:Linux学习👏
👏专栏:C语言初阶👏👏专栏:数据结构👏👏专栏:备战蓝桥杯👏

@TOC


前言

今天我们继续学习算法,加油。这篇文章写的是计数问题。希望这篇可以有幸帮助到你,码字不易,请多多支持。 在这里插入图片描述


题目描述

给定两个整数 aabb,求 aabb 之间的所有数字中 00 ~ 99 的出现次数。

例如,a=1024a=1024b=1032b=1032,则 aabb 之间共有 99 个数如下:

10241024 10251025 10261026 10271027 10281028 10291029 10301030 10311031 10321032

其中 00 出现 1010 次,11 出现 1010 次,22 出现 77 次,33 出现 33 次等等……

输入格式

输入包含多组测试数据。

每组测试数据占一行,包含两个整数 aabb

当读入一行为 0 00\ 0 时,表示输入终止,且该行不作处理。

输出格式

每组数据输出一个结果,每个结果占一行。

每个结果包含十个用空格隔开的数字,第一个数字表示 00 出现的次数,第二个数字表示 11 出现的次数,以此类推。

数据范围

0<a,b<1080<a,b<10^8

输入样例

1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0

输出样例

1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

算法

(数位DP,暴力枚举) O(log10(b)10)O(log_{10}(b) * 10)

这是一道比较典型的数位 DP 的题目,需要分别求出 0~9 在 [1,b] 中的出现次数和在 [1,a-1] 中的出现次数,然后相减即可得到 [a,b] 中的出现次数。具体实现时,可以采用以下方法:

  • 对于一个数字 n,将其分解为若干个数字 d1,d2,d3,...,dk,即 n=d110^(k-1)+d210^(k-2)+...+dk*10^0。
  • 对于每个数字 i,设 cnt(i,n) 表示在 [1,n] 中 i 的出现次数,则 cnt(i,n) 可以通过如下方式
  • 接下来我们来看看代码中的 dgt 函数,这个函数用于计算一个整数有多少位。我们需要计算它的原因是,我们需要在每一位上检查数字出现的情况。因此,我们需要计算出每一个数的位数,以便我们可以从最低位开始进行检查。
int dgt(int n) // 计算整数n有多少位
{
    int res = 0;
    while (n) ++ res, n /= 10;
    return res;
}

最后,我们可以看到主函数的完整代码。在每个测试用例中,我们计算在 ab 之间数字 0~9 出现的次数,然后输出它们。

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);
        for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
        cout << endl;
    }
    return 0;
}

这就是完整的代码。该算法的时间复杂度为 O(log10n)O(log_{10}n),其中 nnaabb 中较大的那个数。空间复杂度为常数级别。

C++ 代码

# include <iostream>
# include <cmath>
using namespace std;

int dgt(int n) // 计算整数n有多少位
{
    int res = 0;
    while (n) ++ res, n /= 10;
    return res;
}

int cnt(int n, int i) // 计算从1到n的整数中数字i出现多少次 
{
    int res = 0, d = dgt(n);
    for (int j = 1; j <= d; ++ j) // 从右到左第j位上数字i出现多少次
    {
        // l和r是第j位左边和右边的整数 (视频中的abc和efg); dj是第j位的数字
        int p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = n / p % 10;
        // 计算第j位左边的整数小于l (视频中xxx = 000 ~ abc - 1)的情况
        if (i) res += l * p; 
        if (!i && l) res += (l - 1) * p; // 如果i = 0, 左边高位不能全为0(视频中xxx = 001 ~ abc - 1)
        // 计算第j位左边的整数等于l (视频中xxx = abc)的情况
        if ( (dj > i) && (i || l) ) res += p;
        if ( (dj == i) && (i || l) ) res += r + 1;
    }
    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);
        for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
        cout << endl;
    }
    return 0;
}

最后

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.把时间尺度拉长,拉长十年看当下

2.不说负面情绪,只描述事实;

3.越专注于过好自己,能量和幸运越会照顾你; 只解决问题,不做没有意义的担心,输了就认;

4.学会原谅自己,要允许自己做错事,允许自己出现情绪波动,我知道你已经很努力很努力在做好了

5.所有你害怕的、想逃避的事情,最终都要面对,既然这样不如选择坦然面对。即使结果不如人愿,没关系,至少这个过程是享受的,而不是一路带着恐惧和害怕。

最后如果觉得我写的还不错,请不要忘记==点赞==✌,==收藏==✌,加==关注==✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚==菜鸟==逐渐成为==大佬==。加油,为自己点赞!