《剑指Offer》,面试题32的题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。
例如输入12,从1到12这些整数中包含1的数字有1、10、11和12,所以1一共出现了5次。
思路分析
1位数,1~9中,1一共出现了1次;
2位数,10~99中,10-19的十位上一共出现了 10*1=10 次,对于每个十位开头的数字 10~19、20~29,每个数个位上出现的是1-9中1出现的次数,共有9个区间 9*1=9 次;
3位数,100~999,100~199 百位上出现了 10**2=100 次,对于每个百位数开头,例如 100~199,200~299,低位上其实就是 0~99 这个区间上1出现的次数,一共9个区间 9*19=171 次;
由此推测,对于1~9,10~99,100~999,每个 n 位数中包含 1 的个数公式为:
- f(1) = 1
- f(2) = 9 * f(1) + 10 ** 1
- f(3) = 9 * f(2) + 10 ** 2
- f(n) = 9 * f(n-1) + 10 ** (n-1)
通过以上分析,我们可以确定对于任意一个给定的数,例如 23456 这个5位数,10000之前的数中包含的个数是确定的了,为f(1)+f(2)+f(3)+f(4),这是一个递归的过程,对此可以求出 1~4 位中包含1的总数,函数如下所示【注意,返回值应该是 f(1)+f(2)+...+f(n) 而非 f(n) 】:
def get_1_digits(n):
"""
获取每个位数之间1的总数
:param n: 位数
"""
if n <= 0:
return 0
if n == 1:
return 1
current = 9 * get_1_digits(n-1) + 10 ** (n-1)
return get_1_digits(n-1) + current
求给定的 n 是几位数的代码如下:
def get_digits(n):
# 求整数n的位数
ret = 0
while n:
ret += 1
n //= 10
return ret
通过上面的分析,我们知道了 23456 中,1~10000 之间一共出现了多少个1。下一步需要分析 10000~23456 中包含的1。
我们首先把最高位单独拿出来分析一下,求出最高位上1的个数。如果最高位是1,则最高位上一共会出现的1的次数是低位上数字+1。例如12345,最高位上一共出现了2346个1。如果最高位大于1,则会一共出现的次数是10000~19999一共10**4个数。
然后,根据最高位的不同,计算出该高位前面的相同位数范围中的所有数中1的个数。例如对于34567,需要计算出 10000~19999,20000~29999 中一的个数,这时候计算1的个数,也就是计算 0~9999 中1的个数,这就可以转化成上面的 f(n) 来计算了。调用上面函数可以直接得到,然后用得到的值和最高位和1的差值(这里最高位是3)相乘就可以了。
分析完上面的部分后,我们现在只剩下最高位后面的部分了,我们发现剩下的部分还是一个整数,例如 23456 剩下了 3456,这时候直接使用递归处理剩下的 3456 就行了。具体代码如下:
def get_1_nums(n):
if n < 10:
return 1 if n >= 1 else 0
digit = get_digits(n) # 位数
low_nums = get_1_digits(digit-1) # 最高位之前的1的个数
high = int(str(n)[0]) # 最高位
low = n - high * 10 ** (digit-1) # 低位
if high == 1:
high_nums = low + 1 # 最高位上1的个数
all_nums = high_nums
else:
high_nums = 10 ** (digit - 1)
all_nums = high_nums + low_nums * (high - 1) # 最高位大于1的话,统计每个多位数后面包含的1
return low_nums + all_nums + get_1_nums(low)
为了比较运行的效率,我用每次遍历循环每个数中1的个数的方法进行了次数比较,发现使用以上方法效率提高了很多的,给定的数越大,效率提升越明显。常规解法如下:
def test_n(num):
# 常规方法用来比较
ret = 0
for n in range(1, num+1):
for s in str(n):
if s == '1':
ret += 1
return ret
使用下面的代码进行了测试,发现效率提升非常明显:
if __name__ == '__main__':
test = 9923446
import time
t = time.clock()
print test_n(test)
print time.clock() - t
t1 = time.clock()
print get_1_nums(test)
print time.clock() - t1
运行结果如下,发现运行速率提升了很多很多倍:
6970095
18.284745
6970095
0.000223999999999