最近偶然看见了这道微软的面试题目,题目是:
统计从1至400亿之间的自然数中含有多少个1?比如1-11中,有1,10,11这三个自然数有4个1。
参考了dmh2002 大大的解题思路,自己总结了一套更高效的方法,现在拿出来给大家品评一下,如有建议请发帖转告。
好了,我们来看看这道题目的解题思路:
* 如果是1位数即1-9 则一定有1个1
* 如果是2位数即1-99 则10-19中一定有10个1,
* 然后10-99会有9个1位数上所计算出来的1的数量,即11,21...91会有9个1,
* 再加上各位上的1个1,
* 则为10+9*1+1=20,
* 如果是3位数即1-999 则100-199中一定有100个1,
* 然后从200-999之间会有9个2位数上算出来的个数,
* 再加上2位数上算出来的个数的数量与各位数的数量,
* 则为100+9*(20)+20=300
根据以上的实际数据我们可以使用大学学过的“递推方法”得到以下公式:
[1] x = n10(n-1)+1 (适用于10的次方数的情况,如:100、1000、10000……)
[2] x = 10n(1+nl/10) (适用于带系数的数字:40、400、4000)
其中:1.n 表示当前数字所在位数,比如:拿4234 中的4做例子,那么4处于千位,则4*103 那么n=3;
2.l表示当前数字的系数,还是拿4234做例子,那么l=4;
故由上面公式我们能过很容易推导出以下的程序(使用flex4):
import mx.collections.ArrayList;
protected function button1_clickHandler(event:MouseEvent):void
{
countNum(Number(input.text));
}
private function countNum(num:Number):void//计算1-num中存在的1的总个数
{
var toStringNumSource:String=num.toString(); //目标数字转化为字符
var median:int=0; //位数
var onesNum:int=0; //目前有几个1在前面
var oneSum:Number=0; //1得总数
var nowNum:int=0; //当前系数
for (var i:int=toStringNumSource.length; i > 0; i--)
{
median=i - 1;
nowNum=int(toStringNumSource.charAt(toStringNumSource.length - i));
if(nowNum != 0){//当前数字不为0时
if (median == 0)//当前位数为0时
{
oneSum+=onesNum * nowNum+1;//在此位之前的1的个数*当前数字+1(这个1是个位数1-9中独有的1个1)
}
else if(nowNum!=1&&median!=0){//当当前数字不为1且位数不为0的时候调用 公式:
oneSum+=(Math.pow(10, median)) * (1 +onesNum * nowNum + (median * nowNum) / 10);
}
else if(nowNum==1&&median!=0){//当当前数字为1且位数不为0的时候调用 公式:
oneSum+=median * (Math.pow(10, (median - 1))) +onesNum * nowNum*Math.pow(10,median)+1;
}
if(nowNum==1){
onesNum++;//当当前数字是1则onesNum加1
}
}
else if(nowNum == 0){//该步没有意义,只是为了好理解故留下来
oneSum+=onesNum * nowNum;
}
}
output.text=oneSum.toString();
}
]]>
由于适用公式直接得出结果,故比较递归的方式更加快速。使用多组数据测试没有发现问题,欢迎各位能有改进意见,不胜荣幸!