问题描述: 在从1到n的正数中1出现的次数(数组) 题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。 例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。 注:这是一道广为流传的google面试题。 |
这道题也是摘自高手July的牛贴《横空出世,席卷Csdn:记微软等100题系列数次被荐[100题维护地址]》。这道题的求解其实并不难,一个一个数字去检查的这种brute force的方式也可以达到目的。不过这样的话效率太低,设input(dm dm-1…… d2 d1),那么时间复杂度至少是O(10M)级别的。我想了一种方法,把问题先做了两次简化,在用简化问题的结果,组合成原问题的解,粗略的时间复杂度估计是O(m)级别的。
第一次简化:限定取值为:9, 99, 999等等,那么input简化为9的个数;
1)首先来想一下最简单的情况,n<=9。
1.1)那么n=0时,count(n)=0;
1.2)1<=n<=9时,count(n) =1。特别的count(9) =1。
简单的,我们n=0~9的结果存到一个10元数组中,以备查询。
int[] DICT = new int[] {0, 1, 1, 1, 1, 1, 1, 1, 1, 1}; |
2)再稍微外推一点,n=99。从下图我们可以看到一点规律。
十位 | 个位 |
0 | 0~9 |
… | 0~9 |
9 | 0~9 |
个位上是0~9的十次循环,那么1的个数就是10*count(9);而十位上是10个0,10个1,10个2,……10个9,我们把10当作因子提出来,可以发现1的个数是10*DICT[9]。那么count(99)= 10*count(9) + 10*DICT[9]
3)再稍微外推一点,n=999。同样的,下图验证了刚才我们发现的规律。
百位 | 十、个位 |
0 | 0~99 |
… | 0~99 |
9 | 0~99 |
十位、个位一起可以看成0~99的十次循环,那么1的个数就是10*count(99);而百位上和刚才算99的时候十位上的情况差不多,所以1的个数是10*count(9)。那么我们就得到 count(999)=100*DICT[9] + 10*count(99)。
4)综上所述,我们可以得到一个递归公式count9(m), m该全9数字的位数,用来计算n=9, 99, 999, 9999……时1的个数:
4.1) count9(1) = 1;
4.1)count9(m) = 10 * count9(m-1)+ (10^(m-1)) * DICT[9]
java代码如下:
public long countOneBy9(int p_NumOf9){ if(p_NumOf9 == 0) { return 0; }
int l_count = DICT[9]; int l_yuXiang = DICT[9]; for (int l_i = 1; l_i < p_NumOf9; l_i++) { l_yuXiang *= 10; l_count = l_count * 10 + l_yuXiang; }
return l_count; } |
第二次简化:稍微扩展一下input:19, 29, 39,……, 199, 299,……, 2999,……等等,可以利用1)的结果。
1)举个例子比如2999,0~299之间的数如下图:
百位 | 十、个位 |
0 | 0~999 |
1 | 0~999 |
2 | 0~999 |
类似的十位、个位上1的个数为10*count(999);而在百位上1的个数为:DICT[2]*1000。于是1的总个数为:countX9(2999)=3*count(999) + DICT[2]*1000。于是我们也得到了一个公式countX9(d, m),d为该数字的最高位,m为该数字中9的位数,用来计算n=d9,d99,d999等等情况下1的个数: countX9(d,m) =(d+1)*count9(m) + DICT[d]*(10^m)
java代码如下:
public long countOneByX9(int p_x, int p_numOfFollowing9){ if(p_x == 0 && p_numOfFollowing9 == 0) { return 0; }
long l_count = (p_x + 1) * countOneBy9(p_numOfFollowing9); if(p_x != 0) { l_count += DICT[p_x] * ((long)Math.pow(10, p_numOfFollowing9)); }
return l_count; } |
让问题回归复杂: 接受任意input(dm dm-1 …… d2d1)
那么我们可以把求解分为几段:
1) 0~(dm - 1)99999中1的个数。比如input是3230,我们先求解0~2999中1的个数。于是可以套用countX9(dm - 1, m-1)。
2) (dm 0 …… 0 0)~ (dm dm-1 …… d2d1) 中1的个数。
2.1)总共有(dm dm-1 …… d2d1) 个数字,其中最高为都是dm。那么如果dm=1,则我们需要在结果中加入(dm dm-1 …… d2 d1)个1;
2.2)去掉最高位后,我们可以递归的调用本函数求解。
java代码如下:
public class N030_GoogleCountOne extends N030_GoogleCountOnebyRange{ public long countOne(int[] p_inputByDigits, long p_inputVal){ int l_count = 0;
int l_leadingIdx = p_inputByDigits.length - 1; //99999 Max, the rest is from 100000 to p_input int l_leadingDigit = p_inputByDigits[l_leadingIdx]; l_count += countOneByX9(l_leadingDigit - 1, l_leadingIdx);
long l_remainVal = p_inputVal - l_leadingDigit * (long)Math.pow(10, l_leadingIdx);
l_count += (l_leadingDigit == 1 ? 1 : 0) * (l_remainVal + 1);
int l_nextLeadingIdx = l_leadingIdx - 1; for (; l_nextLeadingIdx > -1 && p_inputByDigits[l_nextLeadingIdx] == 0; l_nextLeadingIdx--) { //find the next digit != 0 }
if(l_nextLeadingIdx != -1) { int[] l_remainInputByDigits = new int[l_nextLeadingIdx + 1]; System.arraycopy(p_inputByDigits, 0, l_remainInputByDigits, 0, l_remainInputByDigits.length); l_count += countOne(l_remainInputByDigits, l_remainVal); }
return l_count; } |