在问 · 算法题解

233. 数字 1 的个数

题目描述

输入一个整数 n。

把 0 到 n 之间(含两端)的每个整数都按十进制写出来,统计数字 1 一共出现了多少次,返回这个次数。

示例 1
输入:n = 13
输出:6

解释:数字 1 出现在 1、10、11(两次)、12、13 中,共 6 次。

示例 2
输入:n = 0
输出:0

解释:只有 0,里面没有 1。

数据范围

  • 0 <= n <= 10^9

解题思路:逐位计数

逐位统计:对每一位(个位、十位……,位权 mul),算出 0..n 中有多少个数在这一位上是 1。把 n 拆成 high(高位部分)、cur(当前位)、low(低位部分)。高位取 0..high-1 时,低位可以任意取,共 high*mul 个;高位取 high 时要看 cur:cur=0 没有,cur=1 有 low+1 个,cur>=2 有 mul 个。把各位贡献加起来就是答案。

为什么正确

处理完位权 mul 后,count 等于 0..n 中在不超过 mul 的各位上出现的 1 的总数。

复杂度分析

时间 O(log n),空间 O(1)。

易错点

cur=1 时高位取 high 的那一组只有 low+1 个,而不是 mul 个;另外 mul*10 在 32 位整数下会溢出,要用 64 位。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def countDigitOne(self, n: int) -> int:
        count, mul = 0, 1
        while mul <= n:
            high, cur, low = n // (mul * 10), n // mul % 10, n % mul
            if cur == 0:
                count += high * mul
            elif cur == 1:
                count += high * mul + low + 1
            else:
                count += (high + 1) * mul
            mul *= 10
        return count

载入编辑器,自己跑一遍 →

其他解法

逐位计数按 cur 是 0、1 还是更大分类,直接写出每一位的贡献,最快;数位动态规划不需要公式,是通用模板。

解法 2:数位动态规划 时间 O(log² n),空间 O(log² n)

把 n 写成十进制数位,从高位到低位逐位填数。dfs(i, cnt, tight) 表示已填前 i 位、已经有 cnt 个 1、tight 表示前缀是否和 n 相同时,后面所有填法的 1 的总数。tight 为真时本位最多填 n 的这一位,否则最多填 9。不受限的状态用记忆化保存。

比逐位计数写得长,常数也更大。好处是不用推导分类公式,同一个模板能解决“统计 [0, n] 中满足某种数位条件的数”这一类题。

class Solution:
    def countDigitOne(self, n: int) -> int:
        s = str(n)
        L = len(s)
        memo = {}

        def dfs(i, cnt, tight):
            if i == L:
                return cnt
            if not tight and (i, cnt) in memo:
                return memo[(i, cnt)]
            up = int(s[i]) if tight else 9
            res = 0
            for d in range(up + 1):
                res += dfs(i + 1, cnt + (d == 1), tight and d == up)
            if not tight:
                memo[(i, cnt)] = res
            return res

        return dfs(0, 0, True)

动态规划 专题的其他题目

查看 动态规划 全部题目 →