前言
国庆7天长假,自己还是要有学习计划的,前两天需要找回之前的状态,预计10道ACM题目和看完《cracking the coding interview》。话说,还算顺利的拿到了阿里的系统工程师offer,虽然没最终确定,估计问题不大了,毕竟短信都收到了,很开心却让我丧失了很多的动力,趁着国庆长假找回失去的状态,记得刚读研就是利用国庆长假看完《php和myslq开发》,从而走上了开发服务器端应用层API的道路
题目
Write a method to count the number of 2s between 0 and n.
思路一
首先,最简单的思路就是从0到n计算每个数出现2的个数。时间复杂度为0(nlogn)
/**
* 暴力法求解
*
* T = O(nlogn)
*
*/
int count2(int n)
{
int count = 0;
while (n) {
if (n % 10 == 2) count ++;
n /= 10;
}
return count;
}
int calViolentTwo(int n)
{
int i, num;
for (i = 0, num = 0; i <= n; i ++) {
num += count2(i);
}
return num;
}
上述代码最大的问题还是效率,当32bit机器,n为最大正整数2147483647,求1到n之间的2出现的数量耗时相当长久。因此,想提高效率,需要从数字中找规律,参考《编程之美》,顺便吐槽一下校招的笔试和面试题目,大部分都是《剑指offer》和《编程之美》上的,还好我都看完了
思路二
假设一个5位数abcde,我们以百位上的数字为例,即从0到abcde的数中,有多少个数的百位上是2.
当百位c为0时,例如13023,0到43023中哪些数的百位会出现2?我们从小的数开始算起,200-299,1200-1299,2200-2299,....,12200-12299,这1200个数,它们的百位为2,共1300个数,再往下,13200已经大于13023了,因此不再往下。所以当百位为0时,百位出现2的次数只由更高位决定,等于更高数位(13)×当前位数(100) = 1300个
当百位为1时,例如13123,分析跟百位为0是一样的
以上两步综合起来,可以得到以下结论:
当某一位数字小于2时,那么该位出现2的次数为:更高位数字×当前位数
当百位数字为2时,例如13223,那么除了200-299到12200-12299这1300个数外,还有13200-13223,这24个数(低位数字+1).所以,当百位数字为2时,百位出现的次数既受高位影响也受低位影响,结论如下:
当某一位数字等于2时,那么该位出现2的次数为:更高数字×当前位数+低位数字+1
当百位数字大于2时,例如13523,那么固定低位200-299,高位依次从0到13,更1400个数字,因此出现2的次数为:(高位数字+1)×当前位数,结论如下:
当某一位数字大于2时,那么该位出现2的次数为:(更高位数字+1) × 当前位数
根据以上三条结论,可以写出如下代码:
/**
* 数学方法
*
* T = O(lnN/ln10)
*/
int calTwo(int n)
{
int low, high, cur, factor = 1, num = 0;
while (n / factor) {
cur = (n / factor) % 10;
low = n - (n / factor) * factor;
high = n / (factor * 10);
switch (cur) {
case 0 :
case 1 :
num += high * factor;
break;
case 2 :
num += high * factor + low + 1;
break;
default :
num += (high + 1) * factor;
break;
}
factor *= 10;
}
return num;
}