zoukankan      html  css  js  c++  java
  • 统计数字

     题目

           一本书的页码从自然数1开始顺序编码直到自然数n。书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0。例如第6页用6表示而不是06或006。数字统计问题要求对给定书的总页码,计算出书的全部页码中分别用到多少次数字0,1,2,3,.....9。

    来源:王晓东编著的《算法设计与实验题解》

    网上有几篇相关的文章:

    文章1

    来自http://blog.csdn.net/jcwKyl/article/details/3009244

    这个题目有个最容易想到的n*log10(n)的算法。这是自己写的复杂度为O(n*log10(n))的代码:

    1. void statNumber(int n) {
    2.     int i, t;
    3.     int count[10] = {0};
    4.     for(i = 1; i <= n; i++) {
    5.     t = i;
    6.     while(t) {
    7.         count[t%10]++;
    8.         t/=10;
    9.     }
    10.     }
    11.     for(i = 0; i < 10; i++) {
    12.     printf("%d/n", count[i]);
    13.     }
    14. }

    仔细考虑m个n位十进制数的特点,在一个n位十进制数的由低到高的第i个数位上,总是连续出现10^i个0,然后是10^i个1……一直到10^i个9,9之后又是连续的10^i个0,这样循环出现。找到这个规律,就可以在常数时间内算出第i个数位上每个数字出现的次数。而在第i个数位上,最前面的10^i个0是前导0,应该把它们减掉。

    这样,可以只分析给定的输入整数n的每个数位,从面可以得到一个log10(n)的算法,代码如下:

    1. void statNumber(int n) {
    2.     int m, i, j, k, t, x, len = log10(n);
    3.     char d[16];
    4.     int pow10[12] = {1}, count[10] = {0};
    5.     for(i = 1; i < 12; i++) {
    6.     pow10[i] = pow10[i-1] * 10;
    7.     }
    8.     sprintf(d, "%d", n);
    9.     m = n+1;
    10.     for(i = 0; i <= len; i++) {
    11.     x = d[i] - '0';
    12.     t = (m-1) / pow10[len-i]; 
    13.     count[x] += m - t * pow10[len-i]; 
    14.     t /= 10;
    15.     j = 0;
    16.     while(j <= x-1) {
    17.         count[j] += (t + 1) * pow10[len-i];
    18.         j++;
    19.     }
    20.     while(j < 10) {
    21.         count[j] += t * pow10[len - i];
    22.         j++;
    23.     }
    24.     count[0] -= pow10[len-i]; /* 第i个数位上前10^i个0是无意义的 */
    25.     }
    26.     for(j = 0; j < 10; j++) {
    27.     printf("%d/n", count[j]);
    28.     }
    29. }

    通过对随机生成的测试数据的比较,可以验证第二段代码是正确的。
    对两段代码做效率测试,第一次随机产生20万个整数,结果在我的电脑上,第二段代码执行1.744秒。第一段代码等我吃完钣回来看还是没反应,就强行关了它。
    第二次产生了1000个整数,再次测试,结果第一段代码在我的电脑上执行的时间是
    10.1440秒,而第二段代码的执行时间是0.0800秒。
    其原因是第一段代码时间复杂度为O(n*log10(n)),对m个输入整数进行计算,则需要的时间为 1*log10(1) + 2*log10(2) + ... + m*log10(m), 当n > 10时,有
    n*log10(n) > n,所以上式的下界为11+12+....+m,其渐近界为m*m。对于20万个测试数据,其运行时间的下界就是4*10^10。
    同样可得第二段代码对于n个输入数据的运行时间界是n*log10(n)的。

    上面的代码中有个pow10数组用来记录10^i,但10^10左右就已经超过了2^32,但是题目给定的输入整数的范围在10^9以内,所以没有影响。
    原著中给出的分析如下:
    考察由0,1,2...9组成的所有n位数。从n个0到n个9共有10^n个n位数。在这10^n个n位数中,0,1,2.....9第个数字使用次数相同,设为f(n)。f(n)满足如下递推式:
    n>1:
    f(n) = 10f(n-1)+10^(n-1)
    n = 1:
    f(n) =1
    由此可知,f(n) = n*10^(n-1)。
    据此,可从高位向低位进行统计,再减去多余的0的个数即可。

    著者的思想说的更清楚些应该是这样:

    对于一个m位整数,我们可以把0到n之间的n+1个整数从小到大这样来排列:

    000......0

    .............

    199......9

    200......0

    299......9

    .........

    这样一直排到自然数n。对于从0到199......9这个区间来说,抛去最高位的数字不看,其低m-1位恰好

    就是m-1个0到m-1个9共10^(m-1)个数。利用原著中的递推公式,在这个区间里,每个数字出现的次数

    (不包括最高位数字)为(m-1)*10^(m-2)。假设n的最高位数字是x,那么在n之间上述所说的区间共有

    x个。那么每个数字出现的次数x倍就可以统计完这些区间。再看最高位数字的情况,显然0到x-1这些

    数字在最高位上再现的次数为10^(m-1),因为一个区间长度为10^(m-1)。而x在最高位上出现次数就是

    n%10^(m-1)+1了。接下来对n%10^(m-1),即n去掉最高位后的那个数字再继续重复上面的方法。直到

    个位,就可以完成题目要求了。

    比如,对于一个数字34567,我们可以这样来计算从1到34567之间所有数字中每个数字出现的次数:
    从0到9999,这个区间的每个数字的出现次数可以使用原著中给出的递推公式,即每个数字出现4000次。

    从10000到19999,中间除去万位的1不算,又是一个从0000到9999的排列,这样的话,从0到34567之间

    的这样的区间共有3个。所以从00000到29999之间除万位外每个数字出现次数为3*4000次。然后再统计

    万位数字,每个区间长度为10000,所以0,1,2在万位上各出现10000次。而3则出现4567+1=4568次。

    之后,抛掉万位数字,对于4567,再使用上面的方法计算,一直计算到个位即可。

    下面是自己的实现代码:

    1. void statNumber_iterative(int n) {
    2.     int len, i, k, h, m;
    3.     int count[10] = {0};
    4.     int pow10[12] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
    5.     char d[16];
    6.     len = log10(n);     /* len表示当前数字的位权 */
    7.     m = len;
    8.     sprintf(d, "%d", n);
    9.     k = 0;          /* k记录当前最高位数字在d数组中的下标 */
    10.     h = d[k] - '0';     /* h表示当前最高位的数字 */
    11.     n %= pow10[len];        /* 去掉n的最高位 */
    12.     while(len > 0) {
    13.     if(h == 0) {
    14.         count[0] += n + 1;
    15.         h = d[++k] - '0';
    16.         --len;
    17.         n %= pow10[len];
    18.         continue;
    19.     }
    20.     for(i = 0; i < 10; i++) {
    21.         count[i] += h * len * pow10[len-1];
    22.     }
    23.     for(i = 0; i < h; i++) {
    24.         count[i] += pow10[len];
    25.     }
    26.     count[h] += n + 1;
    27.     --len;
    28.     h = d[++k] - '0';
    29.     n %= pow10[len];
    30.     }
    31.     for(i = 0; i <= h; i++) {
    32.     count[i] += 1;
    33.     }
    34.     /* 减去前导0的个数 */
    35.     for(i = 0; i <= m; i++) {  
    36.     count[0] -= pow10[i];
    37.     }
    38.     for(i = 0; i < 10; i++) {
    39.     printf("%d/n", count[i]);
    40.     }
    41. }

    文章2

    http://blog.sina.com.cn/s/blog_6f883b5e0100siih.html

    程序实例1:
    思想:穷举法,就是对从1开始到n进行每个数字的拆分,对每个数字拆分一位就对应统计一次。
    程序实例2:
    思想:排列组合
    原著中给出的解答思想;
    考察由0,1,2...9组成的所有n位数。从n个0到n个9共有10^n个n位数。在这10^n个n位数中,0,1,2.....9第个数字使用次数相同,设为f(n)。f(n)满足如下递推式:
    f(n) = 10f(n-1)+10^(n-1)……………………(n>1)
    f(n) =1…………………………………………(n=1)
    由此可知,f(n)=n*10^(n-1)。
    据此,可从高位向低位进行统计,再减去多余的0的个数即可。

    从个位开始统计
    对于第k位有数字为c情况,需要考虑四个方面:
    (1) 第k位是c的时候,前k-1位里面各个位增加了多少?
         c*(k-1)*10^(k-2)
    (2) 第k位中比c小的各个数字增加各出现多少次?
        10^(k-1)
    (3) 第k位中此数字增加了多少次?
    1+(k-1位对应的数字),例如:325,第三位的3数字增加出现了1+25=26
    (4) 抠除此时出现的第k位为0的数字?
    由于(2)里面增加了以0开始的数字,所以现在抠除,即从0数字出现次数,减去10^(k-1)
    这些是怎么来的呢?(-----可参见《编程之美》--1的数目)
    附上pascal程序:
      program count;
    var a:array[0..9]of longint;
        n,t,s,i,j,k,c,s2:longint;s1:string;
    begin
    readln(n);
    t:=0;str(n,s1);//----------//t表示n的位数,为便于操作将n转化为字符串
    while n<>0 do
      begin n:=n div 10;inc(t);end;//------------//求位数
    for i:=1 to t do//------------//枚举位数
      begin
       val(s1[t+1-i],c);//------------//提取第i位的数字c。注意枚举的i跟实际字符串中的位置有一定出入要用s1[t+1-i],而不是s1[i]!
       s:=1;for j:=1 to i-1 do s:=s*10;//------------//s=10^(i-1)
       for k:=0 to 9 do
        a[k]:=a[k]+(i-1)*c*(s div 10);//------------//上述第一个方面:第k位是c的时候,前k-1位里面各个数字增加的个数
       for k:=0 to c-1 do
        a[k]:=a[k]+s;//------------//上述第二个方面: 第k位中比c小的各个数字增加的个数
       val(copy(s1,t-i+2,i-1),s2);//------------//这里要注意截取第i位后的数时(即前i-1位的数),字符串copy处理要从t-i+2(因为第i为对应其中第t-i+1),长度为i-1!!!用字符串这里比较容易错!
       a[c]:=a[c]+s2+1;//------------//上述第三个方面:第k位中此数字增加的个数
       a[0]:=a[0]-s;//------------//上述第四个方面:挖掉多余的0
      end;
    for i:=0 to 9 do writeln(a[i]);
    end.


    作者:撞破南墙
    出处:http://www.cnblogs.com/facingwaller/
    关于作者:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    托付和事件的使用
    在使用supervisord 管理tomcat时遇到的小问题
    无法安装vmware tools的解决方PLEASE WAIT! VMware Tools is currently being installed on your system. Dependin
    (转)Openlayers 2.X加载高德地图
    (转)openlayers实现在线编辑
    (转) Arcgis for js加载百度地图
    (转)Arcgis for js加载天地图
    (转) 基于Arcgis for Js的web GIS数据在线采集简介
    (转) Arcgis for js之WKT和GEOMETRY的相互转换
    (转)Arcgis for Js之Graphiclayer扩展详解
  • 原文地址:https://www.cnblogs.com/facingwaller/p/2377999.html
Copyright © 2011-2022 走看看