C语言学习笔记20260624-统计数字出现次数(暴力枚举与数位统计法)

C语言学习笔记20260624-统计数字出现次数(暴力枚举与数位统计法)

一、学习目标

掌握计算机解决"区间数字统计"类问题的两种核心算法思想。通过经典的"统计1到n中数字x出现次数"问题,深入理解暴力枚举法的底层逻辑,并学习如何通过数位统计公式法进行算法优化,体会从"逐个检查"到"按位贡献"的思维跃迁。

二、问题拆解与核心逻辑

本题要求计算在区间 1, n 的所有整数中,数字 x(0 ≤ x ≤ 9)共出现了多少次。核心约束条件为:

  1. 区间约束:遍历范围是从 1 到 n。
  2. 统计目标:不仅要统计包含 x 的数字个数,还要统计 x 出现的总次数(例如数字 22 中包含两个 2,应计数 2 次)。

三、方法一:暴力枚举法(逐位检查)

3.1 核心思路

利用 for 循环遍历 1 到 n 的每一个整数。对于每一个整数,通过"取余"和"整除"操作,将其每一位数字拆解出来,逐一与目标数字 x 进行比对。

3.2 代码实现

c 复制代码
#include <stdio.h>
int main()
{
    int n, x;
    scanf("%d %d", &n, &x);
    int count = 0;
    int j = 0;
    for (int i = 1; i <= n; i++)
    {
        j = i; // 使用临时变量j,避免破坏循环变量i
        while (j) // 只要j不为0,就继续拆解它的每一位
        {
            if (j % 10 == x) // 取出当前最低位,判断是否等于x
                count++;
            j /= 10; // 去掉最低位,将下一位拉到最低位
        }
    }
    printf("%d", count);
    return 0;
}

3.3 方法优缺点分析

  • 优点:逻辑极其直观,代码编写简单,不需要复杂的数学推导,是验证小规模数据答案的首选。
  • 缺点:时间复杂度为 O(n × log n)。当 n 达到百万级(10^6)时,运算次数接近千万级,尚可接受;但当 n 达到亿级(10^8)甚至更高时,暴力法会严重超时。

四、方法二:数位统计公式法(数学优化)

4.1 核心思想:按位贡献法

不逐个枚举数字,而是将问题转化为"逐位计算贡献"。对于数字 n 的任意一位(个位、十位、百位......),该位上数字 x 出现的次数,只与**高位(high)、当前位(cur)、低位(low)**三部分有关。时间复杂度仅为 O(log n)。

4.2 代码实现与分类讨论

c 复制代码
#include <stdio.h>

// 计算1~n中数字x出现总次数
long long countX(long long n, int x)
{
    long long res = 0;
    long long base = 1;  // 位权:1、10、100... 充当"探针"
    long long high, cur, low;

    while (base <= n)
    {
        high = n / (base * 10);   // 高位部分
        cur  = (n / base) % 10;   // 当前位数字
        low  = n % base;          // 低位部分

        if (x == 0)
        {
            // x为0时,高位不能取0(不能有前导零),单独修正公式
            if (high == 0) // 当前位是最高位,0不能作为最高位,直接跳过
            {
                base *= 10;
                continue;
            }
            if (cur > x)
                res += (high - 1) * base + base;
            else if (cur == x)
                res += (high - 1) * base + low + 1;
            else
                res += (high - 1) * base;
        }
        else
        {
            // x 1~9通用公式
            if (cur < x)
                res += high * base;
            else if (cur == x)
                res += high * base + low + 1;
            else
                res += (high + 1) * base;
        }
        base *= 10; // 探针右移一位,处理下一位
    }
    return res;
}

int main()
{
    int n, x;
    scanf("%d%d", &n, &x);
    printf("%lld\n", countX(n, x));
    return 0;
}

4.3 核心细节解析

  • base 的作用base 就像一个探针,它的值是多少(1, 10, 100...),就代表当前正在处理哪一位。通过 n / basen % base 就能把 n 按当前位"切开"。
  • x=0 的特殊处理:这是最容易出错的地方。数字的最高位不能为 0(即不能有前导零)。例如在统计百位上的 0 时,高位不能从 0 开始取(如 000xx 实际上是两位数),必须从 1 开始取,因此公式中高位部分需要减去 1。
  • 分类讨论的精髓 :根据当前位 cur 与目标数字 x 的大小关系(小于、等于、大于),推导出不同的数学公式,直接计算该位上 x 出现的总次数。

五、总结与工程实践建议

暴力枚举法是理解计算机穷举思维的基石,其"取余拆解每一位"的技巧在各类算法题中极为通用。但在面对大区间数字统计问题时,数位统计公式法是更优的工程实践。它不仅将时间复杂度从 O(n × log n) 降低到了 O(log n),更展示了"将全局问题分解为局部贡献"这一解决复杂数学问题的通用范式。在实际开发中,当 n 的范围较小时,可采用暴力法快速解题;当 n 达到 10^8 甚至 10^18 级别时,必须采用数位统计法。