题目:输入一个整数 n,求 1~n 这 n 个整数的十进制表示中 1 出现的次数。例如,输入 12,1~12 这些整数中包含 1 的数字有 1、10、11 和 12,1 一共出现了 5 次。
思路 1:穷举
最直接的思路就是:遍历 1~n,找出每个数字中 1 出现的次数。如何找出某个数字中 1 出现的次数呢?对 10 求余,判断数字个位数是否为一,如果结果等于 1 则数字的个位数为 1,否则数字的个位数不为 1;如果这个数字大于 10,则除以 10 之后再进行求余操作。
我们来分析这个算法的时间复杂度,n 这个数字的十进制表示的长度为 $log_{10 }n$,因此求余除法操作的时间复杂度是 O(logn),我们需要遍历 n 次,因此整个算法的时间复杂度是 O(nlogn)。这样的时间复杂度比较高,我们需要思考更快的算法。
思路 2:排列
我们把 n 设置的大一些,让 n=21345,以便于之后的分析。为了更好的阐述这个算法,我们先来就具体的例子来分析一遍。
- 我们首先把分析对象分成两部分,1~21345 分成 1~19999 和 20000~21345
- 先对第一部分 1~19999 进行分析。计算 1 出现的次数实际上可以理解为十进制表示中某一位为 1 的排列的个数。因此,1~19999 中,当万位为 1 时其余 4 位为任意数字的排列的个数为 $10*10*10*10=10^4=10000$;当千位为 1 时,万位只能取 0 或 1,其他位取任意数字,的排列的个数为 $2*10*10*10=2000$;同理,百位为 1、十位为 1、个位为 1 的排列次数都是 $2*10*10*10=2000$。因此,1~19999 中,总的排列的个数为 $10000+2000*4=18000$
- 然后对第二部分 20000~21345 进行分析,因为我们将 1 出现的次数理解成十进制表示中某一位为 1 的排列的个数,所以 20000~21345 可以转换成 0~1345。于是这个部分就变成了求解 1~1345(0 去掉不影响)中 1 出现的次数
- 对于 1~1345 我们可以根据步骤 1 和 2 递归的求解。把 1~1345 分成 1~999 和 1000~1345。效仿于第 2 步骤的分析,1~999 中 1 出现的次数为 $10*10*10=1000$。1000~1345 可以转换成 1~345 加上 345 次
- 对于 1~345 我们可以根据步骤 1 和 2 递归的求解。把 1~345 分成 1~299 和 300~345。效仿于第 2 步骤的分析,1~299 中 1 出现的次数为 $10*10+2*3*10=260$。300~345 可以转换成 1~45
- 对于 1~45 我们可以根据步骤 1 和 2 递归的求解。把 1~45 分成 1~39 和 40~45。效仿于第 2 步骤的分析,1~39 中 1 出现的次数为 $10+4=14$。40~45 可以转换成 1~5
- 1~5 中 1 出现的次数为 1
- 因此,1~21345 中 1 出现的次数为 $18000+1000+345+260+14+1=19620$
根据以上的分析,我们将这个算法进行总结:计算 1 出现的次数实际上可以理解为十进制表示中某一位为 1 的排列的个数,因此我们可以使用递归的方法,将 n 从十进制表示的最高位到最低位一步一步计算 1 出现的次数。这个算法的递归深度是 $log\,n$,其他的操作都是常数级别的,因此总的时间复杂度为 $O(log\,n)$。
实现
1 | public class Solution { |