题目描述

✅ 233. 数字 1 的个数

image-20260928220658724

题意分析

统计从 0 到 n 的所有非负整数中,十进制数字 1 一共出现多少次。统计对象是每一次数位出现,同一个数里不同位置出现的 1 都要分别计入,不能只统计有多少个数包含 1。

上界 n 本身也在统计范围内。n = 0 时没有任何 1,结果为零。逐个展开所有整数会遍历很大的范围,可以改为分别计算个位、十位等每个位置的贡献,再相加。

解法:按十进制位贡献统计

核心思路

[!blue]

固定当前数位的位权 factor,它依次取 1、10、100…。把上界拆成三段:high = n / (factor * 10) 是当前位左侧的高位,cur = n / factor % 10 是当前位,low = n % factor 是右侧低位。它们满足 n = high * (factor * 10) + cur * factor + low。

现在只统计当前位等于 1 的数。当高位小于 high 时,无论低位怎样取值,整个数都小于上界。高位有 high 种选择,每种选择下,低位可以从 0 取到 factor - 1,一共 factor 种,因此先得到固定贡献 high * factor。

剩下只需看高位恰好等于 high 的最后一段。如果 cur == 0,把当前位置成 1 就已经超过上界,没有额外贡献;如果 cur == 1,低位只能从 0 到 low,贡献 low + 1;如果 cur > 1,当前位置成 1 后已经小于上界,低位可以任意取值,再贡献完整的 factor 种。

对每个位置独立执行上述统计并累加。一个整数中的每次 1 都有唯一的位置,所以不会重复计数,也不会漏掉同一个整数在多个位置上的贡献。最高位之前补出的零不会产生新的 1,因此高位从零开始枚举不影响统计。

位权每轮乘十,直到超过 n,说明更高位置已经不可能出现 1。factor * 10 等中间计算使用 64 位整数,避免最高位计算时先发生溢出;在题目给定的 n <= 10^9 范围内,最终结果可以按接口返回整数。

解题步骤

  1. 使用 64 位整数初始化 factor = 1、ans = 0。
  2. 只要 factor <= n,就计算当前位对应的 high、cur、low。
  3. cur == 0 时加入 high * factor;cur == 1 时加入 high * factor + low + 1;cur > 1 时加入 (high + 1) * factor。
  4. 将位权乘十,继续处理更高一位。
  5. 所有数位统计完后返回答案;n = 0 时循环不执行,直接得到零。

代码实现

class Solution {
    public int countDigitOne(int n) {
        long factor = 1;
        long ans = 0;

        while (factor <= n) {
            long high = n / (factor * 10);
            long cur = (n / factor) % 10;
            long low = n % factor;

            if (cur == 0) {
                ans += high * factor;
            } else if (cur == 1) {
                // 当前位恰为一,末轮低位从零到 low,共有 low 加一种。
                ans += high * factor + low + 1;
            } else {
                ans += (high + 1) * factor;
            }

            factor *= 10;
        }

        return (int) ans;
    }
}
func countDigitOne(n int) int {
    factor := int64(1)
    limit := int64(n)
    ans := int64(0)
    for factor <= limit {
        high := limit / (factor * 10)
        cur := (limit / factor) % 10
        low := limit % factor

        if cur == 0 {
            ans += high * factor
        } else if cur == 1 {
            // 当前位恰为一,末轮低位从零到 low,共有 low 加一种。
            ans += high*factor + low + 1
        } else {
            ans += (high + 1) * factor
        }
        factor *= 10
    }
    return int(ans)
}

复杂度分析

  • 时间复杂度:$O(\log(n + 1))$,每个十进制数位只做常数次拆分和计数;n = 0 时为常数操作。
  • 空间复杂度:$O(1)$,只保存位权、三段数值和累计答案。

关键点总结

[!green]

  • 从枚举整数改为枚举数位,逐位累加出现次数。
  • 高位小于上界对应的部分已经完整出现,贡献统一为 high * factor。
  • 高位相同时,当前位决定低位取值是不可选、部分可选还是全部可选。
  • low + 1 中的一对应低位取零,不能因为从零计数而漏掉它。

易错点总结

[!yellow]

  • 把每个含 1 的整数只计一次,会漏掉同一整数的其他数位贡献。
  • 位权循环使用 factor < n,会在上界恰好等于某个位权时漏掉最高位。
  • 当前位为 1 时只加 low,会遗漏低位全部取零的合法数字。
  • 混淆取模边界:当前位对 10 取模,低位对 factor 取模,高位则需要除以 factor * 10。
  • 用窄整数先计算位权乘十,再赋给宽整数,不能避免乘法已经发生的溢出;位权本身应使用 64 位类型。

相似题目

题目 难度 关联与区别
面试题 17.06. 2出现的次数 困难 同样按high/current/low计算每个十进制位置贡献,目标数码从1换成2。
902. 最大为 N 的数字组合 困难 同样按数位分块计数以避免逐个枚举,本题累计某个数码的出现次数,原题统计合法整数个数。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/56802046
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!