1到n中1出现的次数
1到n中数字1出现的次数
【题目】
给定一个整数n,返回从1到n的数字中1出现的个数。
例如:
n=5,1~n为1,2,3,4,5,
那么1出现了1次,所以返回1。
n=11,1~n为1,2,3,4,5,6,7,8,9,10,11,
那么1出现的次数为1(出现1次),10(出现1次),11(有两个1,所以出现了2次),所以返回4。
一般解法
暴力遍历
对于1~n中的每位数,求出数字1的个数。
一共有n个数,对于每个数字i,遍历求数字1为其长度
⌊
l
o
g
10
i
⌋
+
1
\lfloor log_{10}i \rfloor + 1
⌊log10i⌋+1,
因此其时间复杂度为O(NlogN)。
相应代码
# 暴力遍历
# 对于1~n中的每位数,求出数字1的个数,求和
# 时间复杂度为O(N * logN)
def digit_one_appear_count1(n):
count = 0
for i in range(1, n + 1):
count += get_one_number(i)
return count
# 遍历获取数中1的个数
def get_one_number(digit):
res = 0
while digit != 0:
if digit % 10 == 1:
res += 1
digit = digit // 10
return res
# 简单测试
if __name__ == '__main__':
num1 = 5
print(digit_one_appear_count1(num1)) # 1
num2 = 11
print(digit_one_appear_count1(num2)) # 4
num3 = 114
print(digit_one_appear_count1(num3)) # 42
num4 = 21345
print(digit_one_appear_count1(num4)) # 18821
最优解
计数规律
不再依次考察每一个数,而是分析1出现的规律。
将出现每个位置的数字1相加。
假定n
为21345,则数的范围为1~21345
。
将范围划分为1346~21345
、346~1345
、46~345
、6~45
、1~5
。
1346~21345
- 最高位为第5位(最右边第1位从右往左计数,万位);
- 第5位上的数字1有 1 0 4 10^4 104,10000~19999第5位上都有个数字1;
- 第4位上的数字1有
2
×
1
0
3
2 \times 10^3
2×103,
- 13461999、1100011345
- 1134611999、2100021345
- 同理第3位,第2位,第1位上都有个数字 2 × 1 0 3 2 \times 10^3 2×103;
- 因此
1346~21345
数字1的总数为 1 0 4 + 4 × 2 × 1 0 3 = 18000 10^4 + 4 \times 2 \times 10^3 = 18000 104+4×2×103=18000
346~1345
- 最高位为第4位;
- 第4位上的数字1有 345 + 1 = 346 345 + 1 = 346 345+1=346,1000~1345第4位上都有个数字1;
- 第3位上的数字1有 1 × 1 0 2 1 \times 10^2 1×102,1100~1199;
- 同理第2位,第1位上都有个数字 2 × 1 0 2 2 \times 10^2 2×102;
- 因此
346~1345
数字1的总数为 346 + 3 × 1 × 1 0 2 = 646 346 + 3 \times 1 \times 10^2 = 646 346+3×1×102=646
46~345
为最高位+其他位=
1
0
2
+
3
×
2
×
1
0
1
=
160
10^2 + 3 \times 2 \times 10^1 = 160
102+3×2×101=160
6~45
为最高位+其他位=
1
0
1
+
4
×
1
×
1
0
0
=
14
10^1 + 4 \times 1 \times 10^0 = 14
101+4×1×100=14
1~5
为最高位+其他位=
1
0
0
+
0
=
1
10^0 + 0 = 1
100+0=1
1~21345
总共有
18000
+
646
+
160
+
14
+
1
=
18821
18000 + 646 + 160 + 14 + 1 = 18821
18000+646+160+14+1=18821个数字1
规律小结
- 整体范围划分为部分范围,再求和;
- 部分范围为最高位与其他位之和;
- 最高位i若大于1则为
1
0
i
−
1
10^{i-1}
10i−1,否则为
1
0
i
−
1
10^{i-1}
10i−1余数加1,详见
1346~21345
和346~1345
求最高位数字1; - 其他位作为一个整体求值:最高位 × \times ×其他位个数 × 1 0 i − 2 \times 10^{i-2} ×10i−2;
- 最精妙之处在于求其他位时划分数构成一个循环,使计数方便(
1~21345
整体范围的划分和部分范围的其他位计算) - 时间复杂度为
O
(
l
o
g
N
×
l
o
g
N
)
O(logN \times logN)
O(logN×logN),函数递归调用次数为数的位数
O
(
l
o
g
N
)
O(logN)
O(logN),函数内部实现指数和对数运算为
O
(
l
o
g
N
)
O(logN)
O(logN);
相应代码
import math
# 计数规律
# 时间复杂度为O(logN * logN)
def digit_one_appear_count2(n):
if n <= 0:
return 0
length = math.floor(math.log(n, 10)) + 1
base = 10 ** (length - 1)
first = n // base
if first == 1:
first_ones = n % base + 1
else:
first_ones = base
other_ones = first * (length - 1) * base // 10
return first_ones + other_ones + digit_one_appear_count2(n % base)
# 简单测试
if __name__ == '__main__':
num1 = 5
print(digit_one_appear_count2(num1)) # 1
num2 = 11
print(digit_one_appear_count2(num2)) # 4
num3 = 114
print(digit_one_appear_count2(num3)) # 42
num4 = 21345
print(digit_one_appear_count2(num4)) # 18821
print(digit_one_appear_count2(1)) # 1
有任何疑问和建议,欢迎在评论区留言和指正!
感谢您所花费的时间与精力!