我的个人微信公众号:Microstrong
微信公众号ID:MicrostrongAI
微信公众号介绍:Microstrong(小强)同学主要研究机器学习、深度学习、计算机视觉、智能对话系统相关内容,分享在学习过程中的读书笔记!期待您的关注,欢迎一起学习交流进步!
知乎主页:https://www.zhihu.com/people/MicrostrongAI/activities
题目链接:
题目描述:
解题思路:
(1)方法一:不考虑时间效率的解法
如果在面试的时候碰到这个问题,应聘者大多能想到最直观的方法, 也就是累加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)从数字规律着手明显提高时间效率的解法
《剑指offer》中提到,这种解法为递归的思想,但是书中写得过于复杂,不利于理解,因此我们可以先看代码的实现过程,直接理解这种方法。
# -*- 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》,何海涛著。