• <编程之美>计算0到N中包含数字1的个数


    转自:http://blog.csdn.net/hongjuntu123/article/details/8743266

    有这样一个函数f(n),对于任意正整数n,它表示从 0 到 n 之间出现“1”的个数,比如 f(1) = 1, f(13) = 6,请列出从 1 到 1234567890 中所有的 f(n) = n 的n, 要求准确快速.

    相信很多人都能立刻得出以下的解法:

      for(n:N)

      {

              判断n包含1的个数;

              累加计数器;

      }

    这是最直接的解法,但遗憾的是,时间复杂程度为O(N*logN)。因为还需要循环判断当前的n的各位数,该判断的时间复杂程度为O(logN)。

    接下来就应该思考效率更高的解法了。说实话,这道题让我想起另外一道简单的算法题:

    N为正整数,计算从1到N的整数和。

    很多人都采用了循环求解。然后利用初等数学知识就知道S=N*(N+1)/2,所以用O(1)的时间就可以处理。

    再回到本道题目,同理应该去寻找到结果R与N之间的映射关系。

    分析如下:

    假设N表示为a[n]a[n-1]...a[1],其中a[i](1<=i<=n)表示N的各位数上的数字。

    c[i]表示从整数1到整数a[i]...a[1]中包含数字1的个数。

    x[i]表示从整数1到10^i - 1中包含数字1的个数,例如,x[1]表示从1到9的个数,结果为1;x[2]表示从1到99的个数,结果为20;

    当a[1]=0时,c[1] = 0;

    当a[1]=1时,c[1] = 1;

    当a[1]>1时,c[1] = 1;

    当a[2]=1时,c[2] = a[1] +1+ c[1] + x[1];

    当a[2]>1时,c[2] = a[2]*x[1]+c[1]+10;

    当a[3]=1时,c[3] = a[2]*a[1] +1+ c[2] + x[2];

    当a[3]>1时,c[3] = a[3]*x[2]+c[2]+10^2;

    ......

    以此类推

    当a[i]=1时,c[i] = a[i-1]*...*a[1] +1+ c[i-1]+x[i-1];

    当a[i]>1时,c[i] = a[i]x[i-1]+c[i-1]+10^(i-1);

    实现的代码如下:

    Java代码 
    1. public static int search(int _n)      
    2. {      
    3.     int N = _n/10;      
    4.     int a1 = _n%10,a2;      
    5.     int x = 1;   
    6.     int ten = 10;   
    7.     int c = a1 == 0?0:1;   
    8.     while(N > 0)      
    9.     {      
    10.         a2 = N%10;   
    11.         if(a2 == 0);   
    12.         else if(a2 == 1)c = a1 + 1 + x + c;      
    13.         else c = a2*x + c + ten;      
    14.         a1 = 10*a1 + a2;        
    15.         N /=10;      
    16.         x = 10*x + ten;   
    17.         ten *= 10;    
    18.     }      
    19.     return c;      
    20. }  

    而以上解法的时间复杂程度只有O(logN)

    /////////////////////////////////////////////////

    编程之美之1的数目

    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。这样,1的个数是 5。
    问题是:
    写一个函数f(N) 返回1到N之间出现的“1”的个数,比如f(12)=5。

    这个问题看上去并不是一个困难的问题,因为不需要太多的思考,大家都能找到一个最简单的方法来计算 f(N),那就
    是从1开始遍历到N,将其中每一个数中含有“1”的个数加起来,
    自然就得到了从1到N所有“1”的个数的和.C语言实现如下:

    #include<stdio.h>
    int Count1(int n)
    {
        int iNum=0;
        while(n!=0)
        {
            iNum+=(n%10==1)?1:0;
            n/=10;
                  
        }
        return iNum;
    }

    int Count2(int n)
    {
        int iCount=0;
        for(int i=0;i<=n;i++)
        {
            iCount+=Count1(i);
        } 
        return iCount; 
    }

    int main()
    {
         int x;
         scanf("%d",&x);
         printf("%d",Count2(x));
         return 0;
    }

    但是这个算法的致命问题是效率,它的时间复杂度是O(N)×计算一个整数数字里面“1”的个数的复杂度 = O(N *log2 N),如果给定的 N 比较大,则需要很长的运算时间才能得到计算结果。我试着输入100000000,一共用了大概12秒,貌似有点长,不够比作者说的40S快多了,作者的电脑有点旧…….- .-

    解法二

    <编程之美>先从一些简单的情况开始观察:

    如果N是一位数,可以确定f(N)=1

    如过是二位数,如果 N=13,那么从 1 到 13 的所有数字:1、2、3、4、5、6、
    7、8、9、10、11、12、13,个位和十位的数字上都可能有 1,我们可以将它们分开来考虑,个位出现 1 的次数有两次:1 和 11,十位出现 1 的次数有 4 次:10、11、12 和 13,所以 f(N)=2+4=6。要注意的是 11 这个数字在十位和个位都出现了 1, 但是 11 恰好在个位为 1 和十位为 1 中被计算了两次,所以不用特殊处理,是对的。再考虑 N=23 的情况,它和 N=13 有点不同,十位出现 1 的次数为 10 次,从 10 到 19,个位出现 1 的次数为 1、11 和 21,所以f(N)=3+10=13。通过对两位数进行分析,我们发现,个位数出现 1 的次数不仅和个位数字有关,还和十位数有关:如果 N 的个位数大于等于 1,则个位出现 1 的次数为十位数的数字加 1;如果N 的个位数为 0,则个位出现 1 的次数等于十位数的数字。而十位数上出现 1 的次数不仅和十位数有关,还和个位数有关:如果十位数字等于 1,则十位数上出现 1 的次数为个位数的数字加 1;如
    果十位数大于 1,则十位数上出现 1 的次数为 10。
    f(13) = 个位出现1的个数 + 十位出现1的个数 = 2 + 4 = 6;
    f(23) = 个位出现1的个数 + 十位出现1的个数 = 3 + 10 = 13;
    f(33) = 个位出现1的个数 + 十位出现1的个数 = 4 + 10 = 14;

    f(93) = 个位出现1的个数 + 十位出现1的个数 = 10 + 10 =
    20;

    接着分析 3 位数,

    如果 N = 123:

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

     f(23)= 个位出现 1 的个数 + 十位出现 1 的个数 + 百位出现 1 的次数 = 13 + 20 + 24 = 57;同理我们可以再分析 4 位数、 位数。   根据上面的一些尝试,下面我们推导出一般情况下,从 N 得
    到 f(N)的计算方法: 假设 N=abcde,这里 a、b、c、d、e 分别是十进制数 N 的各个数位上的数字。如果要计算百位上出现 1 的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百
    位(更高位)以上的数字。如果百位上的数字为 0,则可以知道,百位上可能出现 1 的次
    数由更高位决定,比如 12 013,则可以知道百位出现 1 的情况可能
    是 100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,
    一共有 1 200 个。也就是由更高位数字(12)决定,并且等于更高
    位数字(12)×当前位数(100)。

    如果百位上的数字为 1,则可以知道,百位上可能出现 1 的次数不仅受更高位影响,还受低位影响,也就是由更高位和低位共同决定。例如对于 12 113,受更高位影响,百位出现 1 的情况是 100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,一共 1 200个,和上面第一种情况一样,等于更高位数字(12)×当前位数(100)。但是它还受低位影响,百位出现 1 的情况是 12 100~12 113,一共114 个,等于低位数字(123)+1。 如果百位上数字大于 1(即为 2~9),则百位上可能出现 1的次数也仅由更高位决定,比如 12 213,则百位出现 1 的可能性为:100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,12 100~12 199,一共有 1 300 个,并且等于更高位数字+1(12+1)
    ×当前位数(100)。通过上面的归纳和总结,我们可以写出如下的更高效算法来
    计算 f(N):

    #include<stdio.h>
    int Sumls(int n)
    {
        int iCount=0,iFactor=1,iLowerNum=0,iCurrNum=0,iHigherNum=0;
        while(n/iFactor!=0)
        {
            iLowerNum=n-(n/iFactor)*iFactor;
            iCurrNum=(n/iFactor)%10;
            iHigherNum=n/(iFactor*10);
           
            switch(iCurrNum)
            {
                case 0:
                    iCount+=iHigherNum*iFactor;
                    break;
                case 1:
                    iCount+=iHigherNum*iFactor+iLowerNum+1;
                    break;
                default:
                    iCount+=(iHigherNum+1)*iFactor;
                    break;
                           
            }
            iFactor*=10;
           
        }
        return iCount; 
    }

    int main()
    {
        int x;
        scanf("%d",&x);   
        printf("%d",Sumls(x));
        return 0;
    }

    我试着输入100000000瞬间就显示出结果了,编程之美说效率至少提高了40000倍…

  • 相关阅读:
    通过HOOK控制进程的创建
    进程退出前删除自身EXE
    Unicode(UTF&UCS)深度历险
    《12个有趣的C语言问答》评析2
    float的深入剖析
    UML六种关系
    socket1
    ios学习之常见问题记录
    栈和队列总结篇
    Entity Framework做IN查询
  • 原文地址:https://www.cnblogs.com/vipwtl/p/5366877.html
Copyright © 2020-2023  润新知