题目描述

✅ 剑指 Offer 43. 1~n 整数中 1 出现的次数

image-20261001230752568

image-20260928220658724

题意分析

统计从 1 到 n 的所有整数中,十进制数码 1 总共出现多少次。同一个整数若在多个位置含有一,每个位置都分别计数;不是只统计有多少个整数包含一。

题目范围为 0 <= n < 10^9,n = 0 时答案为零。逐个整数转换并统计需要处理接近 n 个数,需要改为直接计算每个十进制位置对答案的贡献。

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

核心思路

[!blue]

固定一个位权 digit,依次取一、十、一百等。将上界拆为 high、cur、low 三部分:high = n / (digit * 10) 是当前位左侧数字,cur = n / digit % 10 是当前位,low = n % digit 是右侧数字。

先统计高位部分小于 high 的情况。高位可以取零到 high-1,每一种都可以让当前位固定为一,而低位从零到 digit-1 自由变化,共有 digit 种。因此这部分贡献统一为 high * digit。

再考虑高位恰好等于 high 的最后一段:若 cur == 0,把当前位换成一已经超过 n,没有新增;若 cur == 1,低位只能取零到 low,新增 low + 1;若 cur > 1,当前位取一后整体已经小于上界,低位可取满全部 digit 种。

这三种情况与代码分支一一对应。前面的高位允许用零补齐,仅用于统一计数;统计的是数码一,补出的前导零不会贡献答案。每个实际出现的一只属于某个具体数位,将所有位的贡献相加即可。

位权、乘法及累计过程使用 64 位整数,避免 digit * 10 的中间计算溢出;最终在本题范围内返回整数结果。

解题步骤

  1. 将答案初始化为零,从 digit = 1 开始。
  2. 用除法和取余拆出当前位的 high、cur、low。
  3. cur == 0 时累加 high * digit;等于一时再加 low + 1;大于一时再加 digit。
  4. 将位权乘十,直到位权超过 n,返回总计数。

代码实现

class Solution {
    public int countDigitOne(int n) {
        long number = n;
        long answer = 0;

        for (long digit = 1; digit <= number; digit *= 10) {
            long high = number / (digit * 10);
            long cur = number / digit % 10;
            long low = number % digit;

            if (cur == 0) {
                answer += high * digit;
            } else if (cur == 1) {
                // 当前位等于一时,最后周期的低位从零到 low 都可选。
                answer += high * digit + low + 1;
            } else {
                answer += (high + 1) * digit;
            }
        }

        return (int) answer;
    }
}
func countDigitOne(n int) int {
    number := int64(n)
    var answer int64
    for digit := int64(1); digit <= number; digit *= 10 {
        high := number / (digit * 10)
        cur := number / digit % 10
        low := number % digit

        if cur == 0 {
            answer += high * digit
        } else if cur == 1 {
            // 当前位等于一时,最后周期的低位从零到 low 都可选。
            answer += high*digit + low + 1
        } else {
            answer += (high + 1) * digit
        }
    }
    return int(answer)
}

复杂度分析

  • 时间复杂度:$O(\log(n+1))$,只遍历十进制数位,每一位做常数次算术运算。
  • 空间复杂度:$O(1)$,保存固定数量的整数变量。

关键点总结

[!green]

  • 改为按数位统计,就不再需要枚举每个整数。
  • 小于上界高位的部分可完整计数,只有高位相同时才受当前位、低位限制。
  • 当前位等于一时,低位从零开始,因此合法取值数是 low + 1。
  • 多个位置的一分别贡献,将各位求和正好得到总出现次数。

易错点总结

[!yellow]

  • 低位应为 n % digit,取模 digit * 10 会把当前位也包含进去。
  • cur == 1 时只加 low 会漏掉低位全零的情况。
  • 不能以 high != 0 决定是否继续,最高位左侧虽为空,最高位本身仍可能贡献。
  • 不应给不足当前位长度的数补入一;补零仅用于表示,实际贡献已由三分支正确限制。
  • 只把答案声明为宽整数还不够,位权及乘法也需要在宽整数类型中执行。

相似题目

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