从1到n整数中1出现的次数(python 实现)

剑指offer中的题目:从1到n整数中1出现的次数

本文是我做这题的思路,整理出来方便自己以后查看,也欢迎大家的讨论。

题目

输入一个正整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11和12。1一共出现了5次。

思路

1.暴力解法

暴力解法就是从头到尾说每个数中1的个数,然后求和。数每个数中1的个数需要的复杂度为O(logn),而每个数都需要数一遍,因此总的算法复杂度为O(nlogn)。

2.递归法

递归法就是把数按位对数进行拆分,一步一步缩小数的规模,比如 n=23417 时,可以把问题拆为1–3417和3418–23417,每步先对后一部分进行计算,前一部分可以看成 n=3417 时的求解。这样就可以先分析下 3418–23417 一共有多少个1呢?首先这些数中包含 10000~19999 这一万个第一个数字为1的数。就是先算下和后面一个数位数相同时第一个数字为1的数一共多少个。后面一个数去掉第一个数字后还有4位,这4为每一个位上都能为1,当其中一位为1时,其它3位可以是 0-9 这10个数中的任何一个,因此一共的个数位 4 ∗ 3 10 4*3^{10} 4310,又由于第一个数位2,因此3418–23417的1的总个数位 2 ∗ 4 ∗ 3 10 2*4*3^{10} 24310,最高位从1到2后面的数重复两遍。我在做这一题的时候,前期有点疑惑的时,在排列组合后面4位数时,这个方法没有考虑重复的情况,假如第一位选了1,第二个数从0-9 10个数都选一遍,然后第二个数选了1时,第一个数又从 0-9 10个数选了一遍,这就出现了两次两个都为1的数,是不是重复计算了?后来仔细想想发现并没有,因为每个位只计算在他的位上出现1的次数,比如刚才说的出现两个数都是1的情况,这时当每个位选1时只算了一次,但其实这个数有两个1,因此后面的再算一次才能把1都统计了,这个问题是统计数字1出现的次数,不是每个数的情况,一个数中存在几个1都要加进去
代码如下:

def numberof1temp(n):
    if 0 < n < 10:
        return 1
    if n <= 0:
        return 0
    sum_1 = 0
    str_n = str(n)
    len_n = len(str_n)
    temp = int(str_n[1:])
    first_number = int(str_n[0])
    numberoffirst = numberof1firstnumber(str_n)
    sum_1 += numberoffirst
    numberofleft = first_number * (len_n - 1) * 10**(len_n-2)

    sum_1 += numberofleft
    sum_1 += numberof1temp(temp)

    return sum_1
    
def numberof1firstnumber(str_n):
    first_number = int(str_n[0])
    if first_number > 1:
        number = 10**(len(str_n)-1)
    else:
        number = int(str_n[1:]) + 1
    return number

3.编程之美上的解法

通过分析,可以发现:
1到10,个位数是1的个数是:1
1到100,十位数是1的个数是:10
1到1000,百位数是1的个数是:100
因此可以通过看这些数包含几个10,几个100,几个1000来计算1出现的次数。还以23417为例,从1到23417要包括多少个10?数数时:1, 2,3, 4, 5, 6, 7, 8, 9,10,然后1,2, 3, 4, 5, 6, 7, 8, 9, 20,再数 1,2, 3, 4, 5, 6, 7, 8, 9, 30。可以看出除了十位数发生了变化,只是相当于个位数重复了一遍。在数数时,大家也经常只数个位数,从1到10数一遍记一下,这样方便好数。十位数同理。 23417 ÷ 10 = 2341 23417\div10=2341 23417÷10=2341 那个位数上出现的1的个数就是 2341 + 1 2341+1 2341+1之所以加1是因为从23410-23417,个位还会再出现一个1。这个思路从数数只数个位数,数完一遍记下数,比较好理解。在写代码时,有一点需要注意的是,循环时数n的值不应该每次变为n//base,而应该改变base为10*base,因为n//base的余数是需要留下来计算的,余数中1出现的次数不能忘了。比如在算十位上1出现的次数时 23417 ÷ 100 = 234 23417\div100=234 23417÷100=234,1出现的次数 234 ∗ 10 234*10 23410,余数17十位上1出现的次数也要算进去的, 234 ∗ 10 234*10 23410只算了 1-23400 中十位上1出现的次数,还有 23401-23417 中十位上1出现的次数。
代码如下:

def numberof1(n):
    sum_1 = 0
    base = 10
    while n//(base//10):
        m = n // base
        b = n % base

        if b >= 2 * base//10:
            b_sum = base//10
        elif b >= 1 * base//10:
            b_sum = b % (base//10) + 1
        else:
            b_sum = 0
        sum_1 += m * base//10 + b_sum
        base *= 10

    return sum_1

总结

在理解算法时,需要记得计算的是所有数中1出现的次数,也就是后面两个算法中分别考虑每个位上1出现的次数。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值