剑指Offer 43. 1~n 整数中 1 出现的次数
解题思路
思路很简单:分成若干个区间分别计算,同时用一个表记录经常要用到的某些区间1的数目。
为了方便理解下面的分析,我先列举几个易推得的结果:
- 1位数
0-9
: 1个; - 2位数
0-99
: 20个 = 1位数所有的1(区间0 ~ 9
) + 全部两位数所有的1( 区间10 ~ 99
分成十位是1和十位是2-9);
具体来说,最高位是1的两位数(即10 ~ 19)全部1的个数:首先每个数的最高位都有1 所以至少有10个(19 - 10 + 1, 或是10^1101),然后再看个位上,这10个数的个位有多少1呢?很显然不就是所有的1位数全部1的个数嘛——即1个;
然后对于最高位是2-9的两位数(即20 ~ 29, 30 ~ 39, …, 90 ~ 99),因为最高位不可能有1,只可能是低位有1,低位只有1位数,那不就是1位数全部的1的个数嘛;
所以对于这些两位数,全部1的个数 = 1 * 8 = 8;
所以2位数只有20个1。 - 3位数
0-999
: 300个 = 两位数所有的1(区间0 ~ 99
) + 三位数所有的1(区间100 ~ 999
分成百位是1 + 百位是2-9);
两位数所有的1 = 20;
最高位是1的三位数(即100 ~ 199)全部1的个数,同理 = (199 - 100 + 1) + 20, 或者写成10^2 + 20102+20, 即百位全是1(10^2102个数),剩下这100个数的十位和个位能有的1就是所有两位数所有的1;
最高位是2-9的三位数(即200 ~ 999)全部1的个数,同理 = 8 * 20,每一个部分(即k00 ~ k99, k为它们的最高位),都只能有两位数(00-99)中所有的1这么多个。
因此可以总结一个公式:
c o u n t [ i ] = c o u n t [ i − 1 ] + ( 1 0 i + c o u n t [ i − 1 ] ) + 8 ∗ ∗ c o u n t [ i − 1 ] = 10 ∗ ∗ c o u n t [ i − 1 ] + 1 0 i count[i] = count[i-1] + (10^i + count[i-1]) + 8 ** count[i-1] = 10 ** count[i-1] + 10^i count[i]=count[i−1]+(10i+count[i−1])+8∗∗count[i−1]=10∗∗count[i−1]+10i,
其中 c o u n t [ i ] count[i] count[i]表示(i+1)位数所有的1的个数, (即从1 到 9 ∗ i 9 ∗ i − 1 . . . 9 1 9 0 9*_i9_*{i-1}...9_19_0 9∗i9∗i−1...9190, 下标表示第几位)。
注, 1 0 i 10^i 10i就是最高位为1的所有**i+1
**位数所有的数($1000…00 - 1999…99)
接下来举个栗子,就很容易知道思路了:
比如num = 2345
,要计算1 ~ num
总共有多少个1
那么我们分区间来看,先计算0 ~ 999
有多少个1,再计算1000 ~ 1999
有多少个1,最后计算2000 ~ 2345
有多少个1。
0 ~ 999
就是之前我们计算过的三位数最多具有的1的数量 = 300个
1000 ~ 1999
:类似的分析,它就是最高位为1的四位数所有的1 = (1999 - 1000 + 1) + 300
2000 ~ 2345
所有的1 = 0-345
所有的1,可以自己计算一下
所以总共有1775个1。
总结
到这里思路应该就出来,要计算1 ~ num
所有的1的数量(为了便于说明,不妨假设num = 67859
:
- 计算这个数是几位数
n = calculateDigits(n)
,同时可以算出它的最高位是几,记为hightestDigitNum
- 首先可以算出所有的
n-1
位数所有的1,即0 ~ 9999
(注意,从上面我们可以看到经常反复使用这种"所有的k位数的所有的1",我们可以先计算缓存起来) - 然后对于
n
位数即 1000..00 67859 1000..00 ~ 67859 1000..00 67859,可以根据最高位继续拆分成两个区间,即拆成10000 ~ 59999
和6000 ~ 67859
,注意区分最高位是1,和最高位是2-9的情况 - 对于最后一种最高位即为
num
最高位数字的区间即如上的60000 ~ 67859
,这个区间所有的1等于0 ~ 7859
所有的1,递归计算即可
(既然是递归,别忘了递归终止条件,num
是1位数)
综上,就可以讨论清楚了,这个hard题还好只要分析清楚了就很简单。
代码
class Solution {
private int[] count = new int[32];
public int countDigitOne(int n) {
count[0] = 1;
int nDigit = calculateDigits(n);
for (int i = 1; i < nDigit - 1; i++) {
count[i] = 10 * count[i-1] + (int)Math.pow(10, i); // 区间 000...00 - 999..99(总共i+1位数)所有的1
}
return countHelper(n);
}
public int countHelper(int num) {
if (num == 0) // 递归终止条件,注意区分0和1-9
return 0;
if (num < 10)
return 1;
int n = calculateDigits(num);
int pow = (int) Math.pow(10, n-1);
int highestDigitNum = num / pow;
int firstCount = count[n-2]; // 0 - 999..99(前面0 - n-1位数所有的1)
int countForOne = pow + count[n-2]; // 1000...00 - 1999..99,最高位为1区间所有的1
int res = firstCount;
if (highestDigitNum == 1) { // 如果最高位为1,则剩下 1000..00 - num`这么多个数
res += (num - pow + 1);
}
else { //highestDigitNum >= 2
res += countForOne; // 计算最高位1的所有的数的全部1的个数
res += (highestDigitNum - 2) * count[n-2]; // 计算最高位分别是2,3 ... highestDigitNum-1的所有数的全部1的个数
}
res += countHelper(num - highestDigitNum * pow); // 计算最后一个最高位为(最高位上的数字)的全部1的个数 == 去掉最高位之后的数的全部1的个数,如32456,最高位为3的所有数全部1的个数 == 0~2456全部1的个数
return res;
}
public int calculateDigits(int num) {
int digits = 0;
while (num > 0) {
digits++;
num /= 10;
}
return digits;
}
}
时间复杂度 O ( k ) O(k) O(k): 最多递归 k k k次, k k k为 n n n的位数
空间复杂度 O ( k ) O(k) O(k): 递归深度最大为 k k k,注意我使用的辅助表只需要占用常量空间,因为int
最多32位
结果:超越100%