牛客网在线编程专题《剑指offer-面试题32》整数中1出现的次数(从1到n整数中1出现的次数)

我的个人微信公众号:Microstrong

微信公众号ID:MicrostrongAI

微信公众号介绍:Microstrong(小强)同学主要研究机器学习、深度学习、计算机视觉、智能对话系统相关内容,分享在学习过程中的读书笔记!期待您的关注,欢迎一起学习交流进步!

知乎主页:https://www.zhihu.com/people/MicrostrongAI/activities

Github:https://github.com/Microstrong0305

个人博客:https://blog.csdn.net/program_developer

  题目链接:

https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

题目描述:

解题思路:

(1)方法一:不考虑时间效率的解法O(n*logn)

如果在面试的时候碰到这个问题,应聘者大多能想到最直观的方法, 也就是累加1到n中每个整数1出现的次数。我们可以每次通过对10求余数判断整数的个位数字是不是1。如果这个数字大于10,除以10之后再判断个位数字是不是1。基于这个思路,我们可以写出如下代码。

# -*- coding:utf-8 -*-
class Solution:
    # 方法一:时间复杂度O(nlogn),logn表示n有logn位
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        count = 0
        for i in range(1, n + 1):
            eachNumCount = 0
            while i:
                if i % 10 == 1:
                    eachNumCount += 1
                i = int(i / 10)
            count += eachNumCount
        return count

 在上述思路中, 我们对每个数字都要做除法和求余运算以求出该数字中1出现的次数。如果输入数字n, n有O(logn)位,我们需要判断每一位是不是1,那么它的时间复杂度是O(n*logn)。当输入n非常大的时候,需要大量的计算,运算效率不高。面试官不会满意这种算法,我们仍然需要努力。

(2)从数字规律着手明显提高时间效率的解法O(logn)

《剑指offer》中提到,这种解法为递归的思想,但是书中写得过于复杂,不利于理解,因此我们可以先看代码的实现过程,直接理解这种方法。

扫描二维码关注公众号,回复: 8869290 查看本文章
# -*- coding:utf-8 -*-
class Solution:
    # 方法二
    def NumberOf1Between1AndN_Solution1(self, n):
        # write code here
        if n <= 0:
            return 0
        return self.NumberOf1(str(n))
    
    def NumberOf1(self, n):
        first = int(n[0])
        length = len(n)
        
        if length == 1 and first == 0:
            return 0
        if length == 1 and first > 0:
            return 1
        
        numFirstDigit = 0
        if first > 1:
            numFirstDigit = self.PowerBase10(length - 1)
        elif first == 1:
            numFirstDigit = int(n[1:]) + 1
        
        numOtherDigits = first * (length - 1) * self.PowerBase10(length - 2)
        
        numRecursive = self.NumberOf1(n[1:])
        
        return numFirstDigit + numOtherDigits + numRecursive
    
    def PowerBase10(self, n):
        result = 1
        for i in range(n):
            result *= 10
        return result


if __name__ == "__main__":
    sol = Solution()
    print(sol.NumberOf1Between1AndN_Solution1(123))

函数PowerBase10的功能是这样的,例如给定n = 4,则它返回10000;n = 3, 返回1000; 依次类推。

理解一个算法最便捷的方式仍然是代入实际的值去计算,我们看n = 123的执行情况:

  • 首先第一次递归调用NumberOf1("123"), first = 1,length = 3, 则numFirstDigit = 23+1=24(line 23)。其实numFirstDigit可以理解为计算百位上出现1的次数是多少,不难理解如果fist > 1,例如n = 200的情况,则执行line 21,结果是100,也就是100~199这100个数的百位上都是1,无论n = 300, 400, 500甚至更大,百位上出现1的次数固定就是100次,但first == 1时不一样,出现1的次数与后两位有关,以123为例,则是100~123共24次。
  • 然后,numOtherDigits = 1 * 2 * 10 = 20,表示十位上出现“1”20次。
  • 其次,就要进入递归过程:numRecursive = NumberOf1(“23”)。进入第二次递归,计算NumberOf1(“23”),first = 2, length = 2,numFirstDigit = 10,numOtherDigits = 2*1*1 = 2。
  • 接着,进入第三次递归:numRecursive = NumberOf1("3")。这时只有1位,执行line 16,返回1。
  • 最后,把所有递归的结果相加:24 + 20 + 12 + 1 = 57。

这是《剑指Offer》上给出的递归解法,当然结果是对的,但是总觉得表述得不是很清晰,它的大段文字的算法描述也不是很想看,总得来说,它是从高位到低位的转换,所以算法实现需要一些技巧,比如先把整型转换成字符型,再转换成整型,初看以为是为了表示大数,仔细看原来是为了方便获取除高位外低位数的值(line 23),但个人认为相对于额外的转换过程其实没有什么必要,除高位外低位数完全可以用别的方法获得。

Reference:

【1】《剑指offer》,何海涛著。

【2】http://chinaunix.net/uid-13246637-id-5190317.html

发布了285 篇原创文章 · 获赞 892 · 访问量 111万+

猜你喜欢

转载自blog.csdn.net/program_developer/article/details/103900559
今日推荐