0
点赞
收藏
分享

微信扫一扫

LeetCode233数字1的个数-容易理解的组合数学方法


tags: LeetCode DSA

题目

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

示例 1:

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

输入:n = 0
输出:0

提示:

0 <= n <= 1e9

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/number-of-digit-one
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

这里面的例子有问题, 说是0~10^9, 结果早就超出1e9了…

首先来看这样一个例子:​​13​​, 怎样来计算数字1的个数呢?

一个比较直观的思路就是先计算​​1~10​​​中数字1的个数, 然后计算​​11~13​​中数字1的个数.

显然: ​​1~10​​​中含有的1的个数为2, 但是在​​11~13​​​中, 应该怎么计算呢? 这里可以进一步分解计算, 先计算个位中含有1的数量, 这里的话就是先计算​​1~3​​​的1的个数, 即为1, 然后对上一位(即十位​​1​​​)分类讨论, 如果是1, 那么1的个数就要加上​​13-10=3​​​, 这样才能计算出总的1的个数,即最终的答案为​​2+1+3=6​​.

有了这个分析, 我们下面只需要找出计算​​1~10​​​这样类型的数字1个数公式即可, 进一步可以推广为计算:

怎么找出这个公式呢, 先以100为例进行分析, 对于100, , 那么由组合数学的基本计数原理, 对十位和个位中能取到的数进行分类讨论, 对于仅含有1个​​​1​​​的数, 1可以固定在个位或者十位或者百位, 这样的情况有个, 这里包含了十位为0的情况, 这样就不用额外讨论10以内的数了, 最后的​​​1​​​是针对百位而言的, 然后是含有两个​​1​​​的数, 显然这样的数只有一个, 即​​11​​​, 那么就可以得到:

这里偷个懒, 我找到了一个计算​​​1~1eN​​​中​​1​​​的数目的公式, 调用了一下LeetCode的提交API, 即:

证明过程也比较Trival, 就是幂级数的求导整理运算, 首先通过逐位计算​​​1​​​的数量得到下面的式子,

写成求和的形式, 可得到:



两边对积分得到:

于是得到:

但是, 只有这一个例子是不行的, 还不能得到的表达式.(因为A需要大等1)

下面再来看​​7000​​​这个数, 同​​100​​​的讨论, 要计算,需要分别计算仅含有1个​​​1​​​的数, 含有两个​​1​​​的数, 含有三个​​1​​​的数以及含有4个​​1​​​的数, 可以得到:

这里要注意对首位(本例中为千位)的讨论, 这里的乘以6指的是只能选​​​0,2,3,4,5,6​​​这6个数, 才能保证1的个数确定, 整理一下, 就可以得到最后的结果:

这里在写代码的时候为方便, 直接用Python的组合数API了, 就是​​​from math import comb​​, 比较方便, 要是自己写的话也不难, 尾递归实现阶乘然后套公式即可.

代码

from math import comb


class Solution:
def countDigitOne(self, n: int) -> int:
if n == 0:
return 0

def count1(A, B):
if A == 0:
return 0
elif B == 0:
return 1
elif A == 1:
return B * 10**(B - 1) + 1
else:
A -= 1
tmp = 9**B + A * 9**(B - 1) * B + B + 1
for i in range(2, B + 1):
tmp += i * (9**(B - i + 1) * comb(B, i - 1)) + \
i * (A * 9**(B - i) * comb(B, i))
return tmp

def digit(n):
# 计算位数
arr = []
while n:
arr.append(n % 10)
n //= 10
return arr
arr = digit(n)
dgt = len(arr)
ans = 0
flag1 = 0
for i in range(dgt - 1, -1, -1):
ans += count1(arr[i], i)
flag1 += 1 if arr[i] == 1 else 0
if i > 0 and flag1:
ans += arr[i - 1] * flag1 * 10**(i - 1)
return

代码部分主要是将数字每一位的位数存成数组, 然后遍历(注意, 这里是逆序), 通过前面的讨论, 判断​​​1​​​所在的位置, 如果有, ​​flag1++​​​, 然后后面的数字都要乘上​​flag1​​​, 即​​arr[i - 1] * flag1 * 10**(i - 1)​​.

执行结果还是不错的, 算是线性时间了:

LeetCode233数字1的个数-容易理解的组合数学方法_商业_17

小结

因为是我自己通过特例一步一步推的, 就感觉还是比较好理解, 但是这样还是比较费时间, 仅供一乐了, 真正的好方法还得看官解.


举报

相关推荐

0 条评论