【LeetCode】233. Number of Digit One

Problem Link

概览

给定一个整数n,求所有小于等于n的非负整数中数字1的个数之和。

暴力求解(TLE)

本题可使用暴力求解枚举每一个小于等于n的非负整数,虽然C语言给定的参数是int,但穷举依然会超时。

时间复杂度:$O(n)$
空间复杂度:$O(1)$

动态规划

标答中给出了一种基于数学的方法,但本题依然可使用动态规划的思想求解。

状态设计:

DP[i]表示前i位数的答案。

状态转移:

这样的状态设计可以使复杂度降至$O(d)$,$d$表示n的位数,但是状态转移过程不单单涉及到前一状态,还涉及到一些已知的结论。

digits[i]表示第i + 1个数字,nums[i]表示前i + 1位数组成的数字(i低到高,i = 0, 1, 2, ..., d - 1),nines[i]表示i9组成的数字的答案,则有

  • nines[i] = nines[i - 1] * 10 + pow(10, i - 1)
  • digits[i] == 0时,DP[i] = DP[i - 1]
  • digits[i] == 1时,DP[i] = DP[i - 1] + nums[i - 1] + 1 + nines[i]
  • digits[i] > 1时,DP[i] = DP[i - 1] + nines[i] * digits[i] + pow(10, i)

具体来说,一个位数为$d$的数$n$可以做如下分解:

$$
n = n_{(d)} * 10^{d - 1} + n’
$$

其中$n’$表示去除最高位所得的数,$n_{(i)}$为整数$n$第$i$位的数字。在算法中DP[i - 1]即表达了数$n’$对应的答案,剩余部分可分为三种情况:

  • $n_{(d)} = 0$,这种情况只会不会出现在最高位,直接相等即可。
  • $n_{(d)} = 1$,则对于任意$0 \le k \le n’$,首位的1都会计算一次次数,这部分的次数为nums[i - 1] + 1次;同时,$10^{d - 1}$的部分则为$d - 1$个$9$组成的数字的答案。
  • $n_{(d)} > 1$,则$n_{(d)} * 10^{d - 1}$的部分包括$n_{(d)}$个$d - 1$个$9$组成的数字的答案以及经过第$d$位为1时数字的个数共$10^{d - 1}$个。

综上所述,算法的时间复杂度为$O(d)$,空间复杂度为$O(d)$,其中$d$为数字的位数。

附录

我的解答