Task 10 统计从1到某个整数之间出现的1的次数

     任务:给定一个十进制的正整数,写下从1开始,到N的所有整数,然后数一下其中出现“1”的个数。

     要求: 写一个函数 f(N) ,返回1 到 N 之间出现的 “1”的个数。例如 f(12) = 5。

               在32位整数范围内,满足条件的“f(N) =N”的最大的N是多少。

1.设计思想:因为上课很多同学都给出了一个一个数地求出所出现的1,最多每个数也就求5、6次,但是所给的整数很大的时候计算机会一下循环递归N次来计算1的次数,这样会导致效率非常低。我们都知道每个位数上都有一定的规律,每一位上出现1的次数都和其前一位和后一位以及当前位上的数字有关系,所以得通过大量的数据一位一位的进行分析,进而找到每个数的规律。

通过对1位数、2位数、3位数,,,进行分析统计,发现如果当前位上的数字为0,1,大于等于1时有不同的情况;则此位上出现的1的次数分别会由更高位数上、更低位或者当前位的数字决定,具体如下:

假设一个数为abcde

如果百位上数字c为0,百位上可能出现1的次数由更高位决定。比如:33033,则可以知道百位出现1的情况可能是:100~199,1100~1199,2100~2199,,.........,32100~32199,一共3300个。可以看出是由更高位数字(12)决定,并且等于更高位数字(ab)乘以 当前位数(100)。

如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:33133,则可以知道百位受高位影响出现的情况是:100~199,1100~1199,2100~2199,,.........,32100~32199,一共3300个。和上面情况一样,并且等于更高位数字(ab)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:33100~33133,一共134个,等于低位数字(cde)+1。

如果百位上数字大于1(2~9),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100~199,1100~1199,2100~2199,...........,11100~11199,12100~12199,一共有1300个,并且等于更高位数字+1(ab+1)乘以当前位数(100)。

除了百位上其他位数也都符合这个规律,所以只需要循环整数的位数次就可以求出最终的结果。

然后第二步实现的时候,开始以为只需要一个循环就行了,让计算机循环。不过由于基数太大所以会用很长时间

将要计算的范围划分为几个区间,然后对每个区间进行计算。比如说:

1到999,先将这些数划分为10个区间:1-99、100-199 … 900-999

由f(999) = 300可知,300以后的区间段可以不计算。当计算200时,可以先计算299,由于f(299)=160<200,200-299的区间可以都不必计算。对要计算的区间,再将它划分为10个区间,重复进行。这样划分的另一个好处是利用公式:f(10^n-1) = n * 10^(n-1),保存上次算得的f(n)直接计算下个数的f(n)。

2.源代码:

#include<iostream>
using namespace std;

int Count(int n){
    
    int count = 0;//1的个数
    
    int CurrentPosition = 1;//当前位
    
    int LowerNum = 0;//低位数字
    
    int CurrNum = 0;//当前位数字
    
    int HigherNum = 0;//高位数字

    while(n / CurrentPosition != 0)
    {
        LowerNum = n - (n / CurrentPosition) * CurrentPosition;//低位数字
        
        CurrNum = (n / CurrentPosition) % 10;//当前位数字
        
        HigherNum = n / (CurrentPosition * 10);//高位数字
        
        if(CurrNum == 0)//如果为0,出现1的次数由高位决定
        {
            count += HigherNum * CurrentPosition;//等于高位数字 * 当前位数
        }
        
        else if(CurrNum == 1)//如果为1,出现1的次数由高位和低位决定
        {
            count += HigherNum * CurrentPosition + LowerNum + 1;//高位数字 * 当前位数 + 低位数字 + 1
        }
        
        else//如果大于1,出现1的次数由高位决定
        {
            count += (HigherNum + 1) * CurrentPosition;//(高位数字+1)* 当前位数
        }
        
        CurrentPosition *= 10;//前移一位
    }
    return count;
}

void main()
{
    int a;
    cout << "请输入一个正整数:";
    cin >> a;
    cout << a;
    cout << "从1到该数字出现的1的次数为:" << Count(a) << endl;
    for (int i = 0; i < 4294967295 ; i++)
    {
        if( Count(i) == i)
        {
            cout << i << "  ";
        }

    }
}

改进之后:

#include<iostream>
using namespace std;

int Count(int n){
    
    int count = 0;//1的个数
    
    int CurrentPosition = 1;//当前位
    
    int LowerNum = 0;//低位数字
    
    int CurrNum = 0;//当前位数字
    
    int HigherNum = 0;//高位数字

    while(n / CurrentPosition != 0)
    {
        LowerNum = n - (n / CurrentPosition) * CurrentPosition;//低位数字
        
        CurrNum = (n / CurrentPosition) % 10;//当前位数字
        
        HigherNum = n / (CurrentPosition * 10);//高位数字
        
        if(CurrNum == 0)//如果为0,出现1的次数由高位决定
        {
            count += HigherNum * CurrentPosition;//等于高位数字 * 当前位数
        }
        
        else if(CurrNum == 1)//如果为1,出现1的次数由高位和低位决定
        {
            count += HigherNum * CurrentPosition + LowerNum + 1;//高位数字 * 当前位数 + 低位数字 + 1
        }
        
        else//如果大于1,出现1的次数由高位决定
        {
            count += (HigherNum + 1) * CurrentPosition;//(高位数字+1)* 当前位数
        }
        
        CurrentPosition *= 10;//前移一位
    }
    return count;
}

inline unsigned count_digits(unsigned long long num)
{
    unsigned long long n = 1;
    unsigned ret = 0;
    while (n <= num) { n *= 10; ++ret; }
    return ret;
}

void get_nums()
{

    unsigned long long x = 1e11 - 1, y;
    unsigned count = 0;
    unsigned idx = 0;
    while (true)
    {
        ++count;
        y = Count(x);
        if (x < y) 
        {

            //x在1到10时,均不满足x<y,所以x>10,下面的k值肯定大于0    
            unsigned k = count_digits(x) - 1;
            x -= (y - x - 1)/k + 1; 
        }
        else if (x > y) { x = y; } 
        else
        {
           cout<< ++idx << ": " << x << endl;
           //break;
           --x;
           if (x == 0) break;
        } 
    }
}

void main()
{
    int a;
    cout << "请输入一个正整数:";
    cin >> a;
    cout << a;
    cout << "从1到该数字出现的1的次数为:" << Count(a) << endl;
    cout << "整数与次数相同的有以下这些,最大值为第一个数:";
    get_nums();
}

3.实验截图:

4.实验总结:

    (1)这个题目跟之前的同样是数学题类型的程序,需要利用大量的来分析统计,从中得出规律,否则就失去了编程的高效性;

    (2)而当完成第一步之后以为第二步很简单,其实不然。感觉当时一定是被成功的喜悦蒙蔽了双眼,只是看它一直在滚动数字,而且也得出了最后的结果,然而却没想到效率的问题,之才发现第二步的设计也包含了好多规律,所以一定要从头到尾保持清醒的头脑。

原文地址:https://www.cnblogs.com/mengxiangjialzh/p/4548613.html