从1到n整数中1出现的次数

来源:互联网 发布:手机淘宝如何修改地址 编辑:程序博客网 时间:2024/04/30 09:55

题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,1一共出现了5次。

这道题目开始是在《编程之美》这本书上面看到的,当时对于它的解析有点不理解,后来在参加大众点评在线笔试的时候正好遇到了这个题目,还好在有限的时间内将解题的时间复杂度达到了最优。最近又在《剑指offer》上面再次遇到这个题目,这里小小的总结一下。

解法一:这个题目看上去并不困难,最直观的解法就是从1开始遍历到n,将其中含有‘1’的个数加起来,自然就得到了从1到n所有‘1’的个数的和。实现代码如下:

unsigned long count(unsigned long n){    long num = 0;    while(n != 0)    {        num += (n%10 == 1)? 1:0;        n /= 10;    }    return num;}unsigned long f(unsigned long n){    unsigned long count = 0;    for(unsigned long i = 1; i < n; i++)    {        count += count(i);    }    return count;}

这个方法实现起来很简单,但是我们对每个数字都要做除法和求余运算以求出该数字中1出现的次数,时间复杂度为O(n*lg n),当n非常大的时候,计算效率很低。所以要提高效率,我们必须摒弃这种遍历的方法,采用另外的思路。

解法二:我们首先分析1位数的情况,n = 3时,f(n)=1;n=0时,f(n)=0; 再看2位数的情况,如果n=13,个位上出现1的次数有2次,1和11,十位上出现1的个数有4次,10,11,12和13,所以f(n)=2+4=6;这里需要注意的是11这个数在十位和个位都出现了1,但是我们对11恰好计算了2次,所以11并不需要特殊处理。再考虑n = 23时的情况,它和n=13有点不同,十位上出现1的次数为10次,从10到19.个位出现1的次数为3次,1,11和21.所以f(n)=3+10=13.通过对两位数进行分析,我们发现,个位数出现1的次数不仅和个位数有关,还和十位数有关:如果n的个位数为0,则个位数出现1的次数等于十位数的数字,如果个位数大于等于1,则个位数出现1的次数为十位数的数字加1.而十位数出现1的次数也类似:如果十位数字等于1,则十位数上出现1的次数为个位数的数字加1,如果十位数大于1,则十位数上出现1的次数为10.下面看几个简单的例子:

f(13) = 2 + 4 = 6; f(23) = 3 + 10 = 13; f(33) = 4 + 10 = 14;.........f(99) = 10 + 10 = 20;

接下来我们分析百位数,千位数,万位数。。。。很快我们能发现一些规律,建议自己去观察总结会更有效果。下面我们以n=abcde为例,a,b,c,d,e分别代表十进制数n的各个数位上的数字。如果要计算百位上出现1的个数。分析:如果百位上面为0,则百位上面可能出现1的次数由更高位决定,比如n=12013,百位出现1的的情况可能有100~199,1100~1199,2100~2199...........11100~11199,一共12*100=1200个。如果百位上面的数字为1,可知百位上面出现1的次数就不仅仅收到高位的影响,还收到低位的影响,比如n = 12113,出现1的情况有前面列举过的1200种,还要加上12100~12113这13+1=14种。如果百位上数字大于1,则百位上可能出现1的次数仅由高位决定,同样除了前面列举的1200种,还要加上12100~12199这100种,一共为(12+1)*100=1300个。通过对百位的归纳和总结,我们对这个规律已经有了很清晰的了解,下面是实现代码:

long sum(unsigned long n){    unsigned long count = 0;    unsigned long factor = 1;    unsigned long lowerNum= 0;    unsigned long currNum= 0;    unsigned long higherNum= 0;        while(n /factor != 0)    {        lowerNum = n - (n / factor)*factor;        currNum = (n / factor) % 10;        higherNum = n /(factor * 10);                switch(currNum)        {        case 0:              count += higherNum * factor;              break;        case 1:              count += higherNum * factor + lowerNum + 1;              break;        default:              count += (higherNum + 1) * factor;              break;        }        factor *=10;    }        return count;}

这个方法只要分析n就能得到f(n),避开了从1到n的遍历,一个数字n有lg(n)位,所以这种方法的时间复杂度为O(lg n)。

在linux下编辑运行结果如下:


上面的解法是《编程之美》上面采取的思路,从低位开始往高位遍历,《剑指offer》上面采用的是从高位往低位遍历,有异曲同工之效,时间复杂度同样是O(lg n)。

原创粉丝点击