应使用按位分析的数学方法而非暴力遍历,时间复杂度从o(n log n)降至o(log n);以n=2314百位为例,拆解为high=2、cur=3、low=14、digit=100,依cur值分三类计算1出现次数,c++中需用long long防溢出。

直接结论:别用暴力遍历,用数位DP或数学归纳法
暴力循环每个数、转字符串再统计 1 的个数,时间复杂度是 O(N log N),N 超过 10⁷ 就明显卡顿。实际工程中 N 动辄 10⁹,必须用 O(log N) 的数学方法——核心是按位分析每一位上数字 1 出现的规律。
关键思路:按位拆解,分 high / cur / low 三段计算
以 N = 2314 为例,当前看百位(即第 3 位,从右往左数,个十百千…),该位数字是 3,此时把它拆成:
– high = 2(高位部分:2314 / 1000 = 2)
– cur = 3(当前位:(2314 / 100) % 10 = 3)
– low = 14(低位部分:2314 % 100 = 14)
– digit = 100(当前位权值)
那么百位上出现 1 的总次数为:
– 若 cur == 0:贡献是 high * digit
– 若 cur == 1:贡献是 high * digit + low + 1
– 若 cur > 1:贡献是 (high + 1) * digit
这个公式本质是统计:在 0~N 中,有多少个数的百位恰好是 1。它把所有可能组合归为三类,避免枚举。
C++ 实现要点:注意整型溢出和边界条件
用 long long 存 digit 和中间结果,因为当 N 接近 INT_MAX 时,digit 可能到 10¹⁰,int 会溢出。
循环终止条件是 digit ,不是 <code>digit —— 否则最高位被跳过。<br>每次迭代更新顺序必须是:<code>high = n / (digit * 10),cur = (n / digit) % 10,low = n % digit。
参考片段:
long long countDigitOne(long long n) {
if (n <h3>容易忽略的坑:N=0、负数、以及 digit 增长越界</h3><p>题目说“1 到 N”,但函数常被误用于 N=0 或负数输入,务必前置校验:<code>if (n <br><code>digit</code> 从 1 开始,乘 10 后可能超过 <code>LLONG_MAX / 10</code>,但只要循环条件写成 <code>digit ,且 <code>n</code> 是非负 <code>long long</code>,就不会触发溢出——因为 <code>digit</code> 第一次超限时,循环已退出。<br>另一个隐性错误:用 <code>int digit = 1</code>,后续 <code>digit *= 10</code> 在 32 位环境里很快溢出为负,导致无限循环。</code></code></p><p>真正难的不是推导公式,而是把 high/cur/low 的划分逻辑和整数除法取整特性对齐——C++ 中 <code>/</code> 对正数就是向下取整,这点刚好契合需求;一旦混入负数,整个逻辑就崩了。</p>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











