【LeetCode】233.数字 1 的个数

131 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情

题目

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

示例 1

输入:n = 13
输出:6

示例 2

输入:n = 0
输出:0

提示

  • 0 <= n <= 109

题解

思路

先观察以下规律, 9 以下的 1 有 1 个 99 以下的 1 相当于从 10 个 9 里面取 1(个位数),又从 10-19 里面的十位数取 1,所以是 10 + 10*1 = 20 999 以下的有 100 + 10 * 20 = 300 以此类推

那么现在给我们一个 n,我们首先知道它能出现多少次一个小于等于它的 9999... 比如说 3278,必然是包含了 999 的而且 0999,1999,2999 一共相当于出现了三次,这就是我们的 res 部分的 999 中 1 的个数乘以第一位的大小。这个时候没考虑的是什么呢?一个是第一位为 3 时其他位变动产生的 1 以及第一位是 1 的所有 (仅讨论第一位上的1)。

前者正是往后的一个递归了 (比如 3278 我们需要知道 278 能组成多少 1,它是第一位为 3 的 1 的个数),而后者,要讨论是否大于 1,这就好比 3278 中是包含 1000-1999 的所有千位数的 1 的,但是 1278 中只有 279 个千位数的 1。所以我们在返回的时候,判断他是不是大于 1 然后加不同的个数即可。

注意:n 的第一位是不可能为 0 的,这是整数的一个性质,所以这保证了我们第一位要么等于 1,要么大于 1.

代码

class Solution:
    def countDigitOne(self, n: int) -> int:
        if n < 10:
            return 1 if n else 0
        num = str(n)
        x = len(num) - 1
        nxt = int(num[1:])
        res = self.f(x) * int(num[0]) + self.countDigitOne(nxt)
        # 第一位大于1,包含那位为1的所有1的个数是10**x ; 否则是num后面的部分再加上"10000.."这个情况的1
        return res + 10 ** x if int(num[0]) > 1 else res + nxt + 1

    """
    0-9: 1
    0-99: 10 + 10 * 1 = 20
    0-999: 100 + 10 * 20 = 300
    0-9999: 1000 + 10 * 300 = 4000
    0-99999: 10000 + 10 * 4000 = 50000
    f(i) = 10 ** (i-1) + 10 * f(i-1)
    其实也可以直接写 f(i) = i * 10 ** (i-1)
    """
    @lru_cache(None)
    def f(self, i):
        # return i * 10 ** (i-1)
        return 10 ** (i-1) + 10 * self.f(i-1) if i else 0

结语

业精于勤,荒于嬉;行成于思,毁于随。