1. 题目
输入一个整数n,求1~n这n个数的十进制表示中1出现的次数。例如,输入12, 1~12这些整数中包含1的数字有1, 10, 11和12, 1 一共出现了5次。
2. 解题思路
我的思路
思路1:
第一步:要找到一个数字中有几个1,可以通过求余数,余数为1则有一个1,再求除以10之后的商的余数,依次类推,直到数字变成零。统计余数为1的个数。
第二部:求1~n的整数中1的个数,就遍历地进行第一步,累加次数。
思路2:
思路2是思路1的循环实现
思路3:
将数字转成字符串再操作,毕竟对字符串的处理方式要比数字多得多。
书中的思路
- 针对一个数字21345,分析数字1在 1~ 21345中出现的次数存在何种规律;
- 将此规律转化成代码;
- 考虑各种边界情况。
3. 代码实现
3.1 思路1(递归)
时间复杂度O(nlogn),空间复杂度S(1)。这种的时间复杂度我不太清除咋算。。。。。
# 必须存为可变对象,因为如果存为数字那么是传给函数的只是一个值的副本,并不会修改这个全局变量。
g_count = {'count': 0}
def number_of_between_1_and_n():
"""
时间复杂度O(nlogn),空间复杂度S(1)
"""
for i in range(1, n+1):
number_of_1(i, g_count)
def number_of_1(n, gcount):
"""
n这个数字中出现1的次数
思路1:递归,时间复杂度O(logn),空间复杂度S(n)
:param n: 输入的整数
"""
# 边界条件
if n <= 0 or not isinstance(n, int):
return
b = n % 10 # 获得余数,若余数为1,则次数加1
if b == 1:
gcount['count'] += 1
# 获得商
a = n // 10 # python3 需要用两个除号,否则得到的结果是float
number_of_1(a, gcount)
if __name__ == '__main__':
number_of_between_1_and_n(12, g_count)
print(g_count)
3.2 思路2(循环)
时间复杂度O(nlogn),空间复杂度S(1)。这种的时间复杂度我不太清除咋算。。。。。
def number_of_between_1_and_n():
"""
时间复杂度O(nlogn),空间复杂度S(1)
"""
number = 0
for i in range(1, n+1):
number += number_of_1(i)
return number
def number_of_1(n):
"""
n这个数字中出现1的次数
思路2:循环,时间复杂度O(logn),空间复杂度S(1)
:param n: 输入的整数
"""
num = 0
while n > 0:
# 获得余数,若余数为1,则次数加1
if n % 10 == 1:
num += 1
n = n // 10
return num
if __name__ == '__main__':
res = number_of_between_1_and_n(12)
print(res)
3.3 思路3
时间复杂度O(nlogn),空间复杂度S(1)。这种的时间复杂度我不太清除咋算。。。。。
def number_of_between_1_and_n():
"""
时间复杂度O(nlogn),空间复杂度S(1)
"""
number = 0
for i in range(1, n+1):
number += number_of_1(i)
return number
def number_of_1(n):
"""
n这个数字中出现1的次数
思路3:将数字转成字符串来处理
:param n: 输入的整数
"""
num = 0
for i in str(num):
if '1' == i:
num += 1
return num
if __name__ == '__main__':
res = number_of_between_1_and_n(12)
print(res)
3.4 思路4
时间复杂度O(logn),空间复杂度S(n)。
首先,将整个功能分解为3个子功能。
尚未验证
def number_of_between_1_and_n(n):
"""
时间复杂度O(logn),空间复杂度S(1)
:param n: 输入的整数
"""
# 若这个整数小于零或者根本就不是一个整数的话,直接返回0
if n <= 0 or not isinstance(n, int):
return 0
return number_of_1(list(str(n)))
def number_of_1(ints):
"""
因为python中没有指针,将数字转成list来处理
:param ints: 字符串型数字的list
"""
if not ints:
return 0
# first是当前的最高位数字
first = int(ints[0])
length = len(ints) # 数字有多少位
# 基线条件1,如果只有一位且数字是零,那么直接返回0
if length == 1 and first == 0:
return 0
# 基线条件2,如果只要1为且数字大于零,那么直接返回1
if length == 1 and first > 0:
return 1
# 假设是['2','1','3','4','5']
# num_first_digit是数字10000~19999的第一位中的1的数目
num_first_digit = 0
# 如果最高位数字大于1,该当如何
if first > 1:
# 获取除最高位之外的其它位中出现1的次数,如果数字为21345,那么最高位为1的数字,是10000~19999
num_first_digit = power_base_10(length-1)
# 最高位数字等于1,该当如何
elif first == 1:
num_first_digit = int(''.join(ints[1:])) + 1
# 那么若是length > 1 且 first == 0 呢?例如20345,在传入['0', '3', '4', '5']
# 那么num_first_digit肯定是零
# num_other_digits是1346~21345除第一位之外的数位中的数目
num_other_digits = first * (length - 1) * power_base_10(length -2)
# num_recursive是1~1345中的数目
num_recursive = number_of_1(ints[1:])
return num_first_digit + num_other_digits + num_recursive
def power_base_10(n):
"""
获取除最高位之后的其它位中出现1的次数
:param n: 去掉最高位之后,剩余的数字有几位
去掉最高位之后,有几位,那么这一部分出现1的个数就是10的几次方
"""
result = 1
for i in range(n):
result *= 10
return result
if __name__ == '__main__':
res = number_of_between_1_and_n(12)
print(res)
4. 总结
说实话,这道题太难我不会做。这个规律我是很难分析出来了,想起来脑壳疼。
5. 参考文献
[1] 剑指offer丛书