AcWing 56 从1到n整数中1出现的次数
发布日期:2021-05-28 16:30:44 浏览次数:32 分类:精选文章

本文共 1818 字,大约阅读时间需要 6 分钟。

计算从1到n中数字“1”出现次数的高效方法

问题描述

给定一个整数n,要求计算从1到n中所有整数的十进制表示中数字“1”出现的总次数。

解决思路

为了高效解决这个问题,我们需要采取一种数学上的分解与分析方法,而不是简单地逐一枚举和计算(这种方法在n较大的情况下会导致时间复杂度过高)。以下是详细的分析方法:

数字的结构分解

当我们将一个数用十进制表示时,可以将其视为一个多位数的结构。例如,数123可以表示为百位、十位、个位的三个部分:1,2,3。

我们可以从右到左逐步分析每一位数字的贡献。这个方法的核心是从低位到高位逐步计算每一位数字“1”出现的次数,并结合其所在的位权来计算总贡献。

每一位的贡献分析

假设我们分析的是当前数的第i位(从右往左数,第一位是个位,第二位是十位,依此类推)。我们需要计算以下三种情况:

  • 当当前位数字大于1时。
  • 当当前位数字等于1时。
  • 当当前位数字等于0时。
  • 然后,我们结合当前位的位权来计算总贡献。

    位权计算方法

    对于第i位,位权是10的(i-1)次方。例如,个位的位置是1(10^0),十位的位置是10(10^1),依此类推。

    具体计算步骤

    假设我们有一个数n,十进制表示为d_k d_{k-1} ... d_1,其中d_k是最高位,d_1是个位。

    我们需要对每一位d_i进行分析:

  • 构造左边部分(高位)和右边部分(低位)

    • 左边部分:所有比当前位更高位的数字组成的数。
    • 右边部分:所有比当前位低位的数字组成的数。
  • 计算左边部分的最大值和右边部分的可能值

    • 如果当前位数字等于1,则左边部分可以唯一确定,右边部分可以从000到999任意变化。
    • 如果当前位数字大于1,则左边部分可以从左边最高位到d_{i+1}-1变化,右边部分从000到999任意变化。
    • 如果当前位数字等于0,则左边部分可以从左边最高位到d_{i+1}变化,右边部分从000到999任意变化。
  • 根据上述情况计算当前位的贡献

    • 当前位数字为1时,贡献为右边部分数目加1。
    • 当前位数字大于1时,贡献为左边最大值乘以右边数目。
    • 当前位数字小于1时,无需额外贡献(因为数字不可能是“1”)。
  • 代码实现示例

    以下是一个实现了上述逻辑的C++代码:

    int numberOf1Between1AndN(int n) {    if (n == 0) return 0;    vector
    digits; while (n) { digits.push_back(n % 10); n /= 10; } int ans = 0; for (int i = digits.size() - 1; i >= 0; --i) { int left = 0; int right = 0; int power = 1; for (int j = digits.size() - 1; j > i; --j) { left = left * 10 + digits[j]; } for (int j = i - 1; j >= 0; --j) { right = right * 10 + digits[j]; power *= 10; } ans += left * power; if (digits[i] == 1) { ans += right + 1; } else if (digits[i] > 1) { ans += power; } } return ans;}

    代码解释

  • 处理特殊情况:如果输入n为0,返回0,因为没有数字“1”。
  • 提取数字:将n转换为各个位上的数字。
  • 遍历每一位:从最高位到最低位逐一分析每个数字的贡献。
  • 计算左边部分:左边部分是当前位右边的所有数字组成的数。
  • 计算右边部分:右边部分是当前位左边所有数字组成的数,并根据位权进行计算。
  • 计算贡献并累加:根据当前位数字的值,对总贡献进行调整。
  • 这种方法的时间复杂度与数字的位数有关,而不是与n的大小有关,因此它是高效的。

    上一篇:AcWing 57 数字序列中某一位的数字
    下一篇:AcWing 55 连续子数组的最大和

    发表评论

    最新留言

    表示我来过!
    [***.240.166.169]2025年04月09日 15时04分16秒