zoukankan      html  css  js  c++  java
  • 【转】从1到N这N个数中1的出现了多少次?

    给定一个十进制整数N,求出从1到N的所有整数中出现"1"的个数。

    例如:N=2,1,2出现了1个"1"。

    N=12,1,2,3,4,5,6,7,8,9,10,11,12。出现了5个"1"。

    最直接的方法就是从1开始遍历到N,将其中每一个数中含有"1"的个数加起来,就得到了问题的解。

     1 long CountOne_simple(long n){
     2     long i = 0,j = 1;
     3     long count = 0;
     4     for (i = 0; i <= n; i++){
     5         j = i;
     6         while (j != 0) {
     7             if (j % 10 == 1)
     8                 count++;
     9             j = j / 10;
    10         }
    11     }
    12     return count;
    13 } 

    此方法简单,容易理解,但它的问题是效率,时间复杂度为O(N * lgN),N比较大的时候,需要耗费很长的时间。

    我们重新分析下这个问题,对于任意一个个位数n,只要n>=1,它就包含一个"1";n<1,即n=0时,则包含的"1"的个数为0。于是我们考虑用分治的思想将任意一个n位数不断缩小规模分解成许多个个位数,这样求解就很方便。

    但是,我们该如何降低规模?仔细分析,我们会发现,任意一个n位数中"1"的个位可以分解为两个n-1位数中"1"的个数的和加上一个与最高位数相关的常数C。例如,f(12) = f(10 - 1) + f(12 - 10) + 3,其中3是表示最高位为1的数字个数,这里就是10,11,12;f(132)=f(100 -1) + f(132 - 100) + 33,33代表最高位为1的数字的个数,这里就是100~132;f(232) = 2*f(100 - 1) + f(32) + 100,因为232大于199,所以它包括了所有最高位为1的数字即100~199,共100个。

    综上,我们分析得出,最后加的常数C只跟最高位n1是否为1有关,当最高位为1时,常数C为原数字N去掉最高位后剩下的数字+1,当最高位为1时,常数C为10bit,其中bit为N的位数-1,如N=12时,bit=1,N=232时,bit=2。

    于是,我们可以列出递归方程如下:

    if(n1 == 1)  f(n) = f(10bit-1) + f(n - 10bit)  + n - 10bit+ 1;  else  f(n) = n1*f(10bit-1) + f(n – n1*10bit) + 10bit;

    递归的出口条件为:

    1 if(1<n<10)  return 1;
    2 else if (n == 0) return 0;

    基于此,编写如下代码:

     1 long CountOne(long n){
     2     long count = 0;
     3     if (n == 0)
     4         count = 0;
     5     else if (n > 1 && n < 10)
     6         count =  1;
     7     else{
     8         long highest = n;//表示最高位的数字
     9         int bit = 0;
    10         while (highest >= 10){
    11             highest = highest / 10;
    12             bit++;
    13         }
    14         int weight = (int)pow(10, bit);//代表最高位的权重,即最高位一个1代表的大小
    15         if (highest == 1){
    16             count = CountOne(weight - 1)+ CountOne(n - weight)+ n - weight + 1;
    17         }
    18         else{
    19             count = highest * CountOne(weight - 1)+ CountOne(n - highest * weight)+ weight;
    20         }
    21     }
    22     return count;
    23 }

    还有就是,我尝试了许久也没有计算出此算法的时间复杂度到底是多少,似乎是O(log2N),我并不确定,希望知道的高手能给予解答。此算法的优点是不用遍历1~N就可以得到f(N)。经过我测试,此算法的运算速度比解法一快了许多许多,数字在1010内时,算法都可以在毫秒级内结束,而解法一在计算109时,时间超过了5分钟。但此算法有一个显著的缺点就是当数字超过1010时会导致堆栈溢出,无法计算。

    解法二告诉我们1~ N中"1"的个数跟最高位有关,那我们换个角度思考,给定一个N,我们分析1~N中的数在每一位上出现1的次数的和,看看每一位上"1"出现的个数的和由什么决定。

    1位数的情况:在解法二中已经分析过,大于等于1的时候,有1个,小于1就没有。

    2位数的情况:N=13,个位数出现的1的次数为2,分别为1和11,十位数出现1的次数为4,分别为10,11,12,13,所以f(N) = 2+4。N=23,个位数出现的1的次数为3,分别为1,11,21,十位数出现1的次数为10,分别为10~19,f(N)=3+10。

    由此我们发现,个位数出现1的次数不仅和个位数有关,和十位数也有关,如果个位数大于等于1,则个位数出现1的次数为十位数的数字加1;如果个位数为0,个位数出现1的次数等于十位数数字。而十位数上出现1的次数也不仅和十位数相关,也和个位数相关:如果十位数字等于1,则十位数上出现1的次数为个位数的数字加1,假如十位数大于1,则十位数上出现1的次数为10。

    3位数的情况:

    N=123,个位出现1的个数为13:1,11,21,…,91,101,111,121。十位出现1的个数为20:10~19,110~119。百位出现1的个数为24:100~123。

    我们可以继续分析4位数,5位数,推导出下面一般情况: 假设N,我们要计算百位上出现1的次数,将由三部分决定:百位上的数字,百位以上的数字,百位一下的数字。

    如果百位上的数字为0,则百位上出现1的次数仅由更高位决定,比如12013,百位出现1的情况为100~199,1100~1199,2100~2199,…,11100~11199,共1200个。等于更高位数字乘以当前位数,即12 * 100。

    如果百位上的数字大于1,则百位上出现1的次数仅由更高位决定,比如12213,百位出现1的情况为100~199,1100~1199,2100~2199,…,11100~11199,12100~12199共1300个。等于更高位数字加1乘以当前位数,即(12 + 1)*100。

    如果百位上的数字为1,则百位上出现1的次数不仅受更高位影响,还受低位影响。例如12113,受高位影响出现1的情况:100~199,1100~1199,2100~2199,…,11100~11199,共1200个,但它还受低位影响,出现1的情况是12100~12113,共114个,等于低位数字113+1。

    综合以上分析,写出如下代码:

     1 long CountOne2(long n){
     2     long count = 0;
     3     long i = 1;
     4     long current = 0,after = 0,before = 0;
     5     while((n / i) != 0){          
     6         current = (n / i) % 10;
     7         before = n / (i * 10);
     8         after = n - (n / i) * i;
     9         if (current > 1)
    10             count = count + (before + 1) * i;
    11         else if (current == 0)
    12             count = count + before * i;
    13         else if(current == 1)
    14             count = count + before * i + after + 1;
    15         i = i * 10;
    16     }
    17     return count;
    18 }

    此算法的时间复杂度仅为O(lgN),且没有递归保存现场的消耗和堆栈溢出的问题

  • 相关阅读:
    hadoop中namenode发生故障的处理方法
    开启虚拟机所报的错误:VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run the program, access all directories the program uses, and access all directories for temporary fil
    Hbase的安装与部署(集群版)
    分别用反射、编程接口的方式创建DataFrame
    用Mapreduce求共同好友
    SparkSteaming中直连与receiver两种方式的区别
    privot函数使用
    Ajax无刷新显示
    使用ScriptManager服务器控件前后台数据交互
    数据库知识
  • 原文地址:https://www.cnblogs.com/tswcypy/p/3764702.html
Copyright © 2011-2022 走看看