计数问题
题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。
例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。
分析:这是一道广为流传的google面试题。用最直观的方法求解并不是很难,但遗憾的是效率不是很高;而要得出一个效率较高的算法,需要比较强的分析能力,并不是件很容易的事情。当然,google的面试题中简单的也没有几道。
一、直觉法
首先我们来看最直观的方法,分别求得1到n中每个整数中1出现的次数,
然后把它们加起来就行了。
然而, google 的面试不是这么容易 让你过关的。
因为这种方法的复杂度是 O( n ) ,对较大的整数,运算时间太长
二、 新方法
我们通过分析数字的规律,找出加快计算速度的方法。
0 - 9 之间 所有数字中 出现的 1 为 1 个
0 –99 十位的1 有 10个
去掉十位后, 有10组 0~9
所以 共有 10 + 10 个
0 ~ 999 之间
百位的1 有 100 = 10^2 个
去掉百位后有 10组 : 0~99
所以共有 10^2 + 10 * ( 10^1 + 10 )
0 ~ 9999 之间千位为1的共 1000= 103 个1
去掉千位后 共有10组 0~999
所以有: 10^3 + 10*( 10^2 + 10 * ( 10^1 + 10*1 ) ) 个1
计算 0 ~ 9999…9
┣──╁ 共 n 个 9 中1的个数F9( n) 的计算方案
┣──╁ 共 n 个 9 中1的个数F9( n) 的计算方案
F9( 0 ) = 0
F9( 1) = 1
F9( 2) = 10 + 10 * F9(1)
F9( 3) = 10^2 + 10 * F9(2)
F9( n ) = 10^n-1 + 10 * F9( n-1)
计算 1 ~ abcdefg 的方法
令 1 ~ abcdefg 之间 出现 1 的个数 记为 Q( abcdefg )
设 m = len( “abcdefg”)
若 a=1 则有 F9( m-1 ) + (bcdefg+1) 个高位 1 + Q( bcdefg )
否则 a>1 ( a=0不考虑)
则有 F9( m-1) + 10^( m-1) 个 高位1 +
(a-1)* ( F9(m-1) ) + Q( bcdefg )
即: a * F9(m-1) + 10^( m-1 )个 高位1 + Q( bcdefg )
注:
F9( m-1) 是 从1 到 (m-1)个9 组成的数 之间的Q
如 F9( 2 ) 相当于 Q(99)
此处为了提方运算速度,我们把 F9 提前算出。
#coding=gbk
import time
def nowTime():
return time.time()
import time
def nowTime():
return time.time()
# 题目:从1到n的正数中1出现的次数
# 这是一道广为流传的google面试题。
# 这是一道广为流传的google面试题。
# 直觉算法
# 计算一个正数n中 1 出现的次数
def calcOnesFromOneNumber ( n ):
s = str( n )
m = 0
for x in s:
if x=="1":
m = m+1
return m
# 计算一个正数1 到 n 中 1 出现的次数
# 该算法复杂度 > O( n )
def QQ( n ):
sm = 0
for i in range(1,n+1):
sm = sm + calcOnesFromOneNumber( i )
return sm
# 计算一个正数n中 1 出现的次数
def calcOnesFromOneNumber ( n ):
s = str( n )
m = 0
for x in s:
if x=="1":
m = m+1
return m
# 计算一个正数1 到 n 中 1 出现的次数
# 该算法复杂度 > O( n )
def QQ( n ):
sm = 0
for i in range(1,n+1):
sm = sm + calcOnesFromOneNumber( i )
return sm
# F10 依次为 10 的 0、1、2.。。次方的值
# F9 依次为 当n= 0, 9、99、999、9999....时 1~ n 的中出现 1 的次数
F10 = [ 1 ]
F9 = [ 0 ]
T = 1
for i in range( 1,30):
F9.append( T + 10*F9[ i-1])
T = T * 10
F10.append( T )
F10 = [ 1 ]
F9 = [ 0 ]
T = 1
for i in range( 1,30):
F9.append( T + 10*F9[ i-1])
T = T * 10
F10.append( T )
# 新方法 计算 一个正数1 到 n 中 1 出现的次数
def Q( n ):
if n<1:
return 0
if (1<=9):
return 1
s = str( n )
m = len( s)
n1 = int(s[1:])
if s[0]=='1':
return F9[ m-1 ] + (n1+1) + Q( n1 )
else:
return int(s[0]) *F9[ m-1 ] + F10[m-1] + Q( n1 )
n = 123456
print ("-----新方法计算 1 至 ", n,"之间 1 出现的次数")
a = nowTime()
print( Q(n) )
b=nowTime()
print( "用时:", b-a)
print ("-----直觉法计算 1 至 ", n,"之间 1 出现的次数")
print ( QQ( n ))
c=nowTime()
print( "用时:", c-b)
print ( "*******************************")
n = 21345
print ("-----新方法计算 1 至 ", n,"之间 1 出现的次数")
a = nowTime()
print( Q(n) )
b=nowTime()
print( "用时:", b-a)
print ("-----直觉法计算 1 至 ", n,"之间 1 出现的次数")
print ( QQ( n ))
c=nowTime()
print( "用时:", c-b)
def Q( n ):
if n<1:
return 0
if (1
return 1
s = str( n )
m = len( s)
n1 = int(s[1:])
if s[0]=='1':
return F9[ m-1 ] + (n1+1) + Q( n1 )
else:
return int(s[0]) *F9[ m-1 ] + F10[m-1] + Q( n1 )
n = 123456
print ("-----新方法计算 1 至 ", n,"之间 1 出现的次数")
a = nowTime()
print( Q(n) )
b=nowTime()
print( "用时:", b-a)
print ("-----直觉法计算 1 至 ", n,"之间 1 出现的次数")
print ( QQ( n ))
c=nowTime()
print( "用时:", c-b)
print ( "*******************************")
n = 21345
print ("-----新方法计算 1 至 ", n,"之间 1 出现的次数")
a = nowTime()
print( Q(n) )
b=nowTime()
print( "用时:", b-a)
print ("-----直觉法计算 1 至 ", n,"之间 1 出现的次数")
print ( QQ( n ))
c=nowTime()
print( "用时:", c-b)
运行结果
-----新方法计算 1 至 123456 之间 1 出现的次数
93553
用时: 0.010999917984
-----直觉法计算 1 至 123456 之间 1 出现的次数
93553
用时: 0.495000123978
*******************************
-----新方法计算 1 至 21345 之间 1 出现的次数
18821
用时: 0.0119998455048
-----直觉法计算 1 至 21345 之间 1 出现的次数
18821
用时: 0.174000024796
93553
用时: 0.010999917984
-----直觉法计算 1 至 123456 之间 1 出现的次数
93553
用时: 0.495000123978
*******************************
-----新方法计算 1 至 21345 之间 1 出现的次数
18821
用时: 0.0119998455048
-----直觉法计算 1 至 21345 之间 1 出现的次数
18821
用时: 0.174000024796
讨论:
直觉法程序比较简单, 用时长一点,但可用作对新方法的验证。
评论
发表评论