1到n中1出现的次数

1到n中1出现的次数

1到n中数字1出现的次数

【题目】
给定一个整数n,返回从1到n的数字中1出现的个数。

例如:
n=5,1~n为1,2,3,4,5,
那么1出现了1次,所以返回1。

n=11,1~n为1,2,3,4,5,6,7,8,9,10,11,
那么1出现的次数为1(出现1次),10(出现1次),11(有两个1,所以出现了2次),所以返回4。


一般解法

暴力遍历

对于1~n中的每位数,求出数字1的个数。
一共有n个数,对于每个数字i,遍历求数字1为其长度 ⌊ l o g 10 i ⌋ + 1 \lfloor log_{10}i \rfloor + 1 log10i+1
因此其时间复杂度为O(NlogN)。


相应代码
# 暴力遍历
# 对于1~n中的每位数,求出数字1的个数,求和
# 时间复杂度为O(N * logN)
def digit_one_appear_count1(n):
    count = 0
    for i in range(1, n + 1):
        count += get_one_number(i)
    return count

# 遍历获取数中1的个数
def get_one_number(digit):
    res = 0
    while digit != 0:
        if digit % 10 == 1:
            res += 1
        digit = digit // 10
    return res
 
# 简单测试
if __name__ == '__main__':
    num1 = 5
    print(digit_one_appear_count1(num1))  # 1
    num2 = 11
    print(digit_one_appear_count1(num2))  # 4
    num3 = 114
    print(digit_one_appear_count1(num3))  # 42
    num4 = 21345
    print(digit_one_appear_count1(num4))  # 18821

最优解

计数规律

不再依次考察每一个数,而是分析1出现的规律。
将出现每个位置的数字1相加。
假定n21345,则数的范围为1~21345
将范围划分为1346~21345346~134546~3456~451~5
1346~21345

  • 最高位为第5位(最右边第1位从右往左计数,万位);
  • 第5位上的数字1有 1 0 4 10^4 104,10000~19999第5位上都有个数字1;
  • 第4位上的数字1有 2 × 1 0 3 2 \times 10^3 2×103
    • 13461999、1100011345
    • 1134611999、2100021345
  • 同理第3位,第2位,第1位上都有个数字 2 × 1 0 3 2 \times 10^3 2×103
  • 因此1346~21345数字1的总数为 1 0 4 + 4 × 2 × 1 0 3 = 18000 10^4 + 4 \times 2 \times 10^3 = 18000 104+4×2×103=18000

346~1345

  • 最高位为第4位
  • 第4位上的数字1有 345 + 1 = 346 345 + 1 = 346 345+1=346,1000~1345第4位上都有个数字1;
  • 第3位上的数字1有 1 × 1 0 2 1 \times 10^2 1×102,1100~1199;
  • 同理第2位,第1位上都有个数字 2 × 1 0 2 2 \times 10^2 2×102
  • 因此346~1345数字1的总数为 346 + 3 × 1 × 1 0 2 = 646 346 + 3 \times 1 \times 10^2 = 646 346+3×1×102=646

46~345最高位+其他位= 1 0 2 + 3 × 2 × 1 0 1 = 160 10^2 + 3 \times 2 \times 10^1 = 160 102+3×2×101=160

6~45最高位+其他位= 1 0 1 + 4 × 1 × 1 0 0 = 14 10^1 + 4 \times 1 \times 10^0 = 14 101+4×1×100=14

1~5最高位+其他位= 1 0 0 + 0 = 1 10^0 + 0 = 1 100+0=1

1~21345总共有 18000 + 646 + 160 + 14 + 1 = 18821 18000 + 646 + 160 + 14 + 1 = 18821 18000+646+160+14+1=18821个数字1

规律小结

  • 整体范围划分为部分范围,再求和;
  • 部分范围为最高位与其他位之和;
  • 最高位i若大于1则为 1 0 i − 1 10^{i-1} 10i1,否则为 1 0 i − 1 10^{i-1} 10i1余数加1,详见1346~21345346~1345求最高位数字1;
  • 其他位作为一个整体求值:最高位 × \times ×其他位个数 × 1 0 i − 2 \times 10^{i-2} ×10i2
  • 最精妙之处在于求其他位时划分数构成一个循环,使计数方便(1~21345整体范围的划分和部分范围的其他位计算)
  • 时间复杂度为 O ( l o g N × l o g N ) O(logN \times logN) O(logN×logN),函数递归调用次数为数的位数 O ( l o g N ) O(logN) O(logN),函数内部实现指数和对数运算为 O ( l o g N ) O(logN) O(logN)
相应代码
import math
# 计数规律
# 时间复杂度为O(logN * logN)
def digit_one_appear_count2(n):
    if n <= 0:
        return 0
    length = math.floor(math.log(n, 10)) + 1
    base = 10 ** (length - 1)
    first = n // base
    if first == 1:
        first_ones = n % base + 1
    else:
        first_ones = base
    other_ones = first * (length - 1) * base // 10
    return first_ones + other_ones + digit_one_appear_count2(n % base)

# 简单测试
if __name__ == '__main__':    
    num1 = 5
    print(digit_one_appear_count2(num1))  # 1
    num2 = 11
    print(digit_one_appear_count2(num2))  # 4
    num3 = 114
    print(digit_one_appear_count2(num3))  # 42
    num4 = 21345
    print(digit_one_appear_count2(num4))  # 18821

    print(digit_one_appear_count2(1))  # 1

有任何疑问和建议,欢迎在评论区留言和指正!

感谢您所花费的时间与精力!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值