Google面试题:计算从1到n的正数中1出现的次数

题目:

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

  找工作,准备看写题目,题目说是Google面试题,遂很认真地自己做了下。




找规律:

  其实可以从中找出数列的规律。求从1到n数字中的1共有多少个,会想到按照数字的位数来观察观察,比如1位数字里(从1到9)共有1个,记W[1]=1;2位数字里(01到99)共有W[2]个,3位数字(001到999)共有W[3]个,定义如下数组:

enum{N = 4};
int W[N];        //数组含义:下标 i 代表整数位数是 i 的共有包含多少个 1,如 i 是 2 代表[1, 99]区间全部整数共包含多少个 1

 要是把这个数组的值全部求到了,那么就好办了。下面找这个数组的规律。

  先看看W[3]与W[2]的关系,如下图:

  

上图左边是一个三位数的第一位的所有情况,即从0到9,右边是后两位的所有情况,从00到99,这就是从000到999所有数字的情况了。注意:对于一个三位数,左边是10种情况,即从0到9,右边的每个从00到99包含的1的个数不就是W[2]吗?再来看三位数字当左边一位是1的情况,这时,数字为从100到199,这100一个数字每个最左边都是一个1。综上:W[3] = 10 * W[2] + 100

  同理可以分析W[4] = 10 * W[3] + 1000,这后面加的100、1000其实就是10的(位数-1)次方。我们猜测:W[n+1] = 10 * W[n] + 10^n(n>=0,W[0]=0)。可以当做数列证明的。




运算:

  找到规律就好办了,一下子就可以把W数组求出来了。但是对于一个数字呢?比如768,如下:

一:先算从000到699的个数,不就 7 * W[2] + 100 嘛,道理同上。

二:再算从700到768,因为最前面的 7 已经不贡献 1 了,所以这个相当于00到68了,其中00到59是 6 * W[1] + 10。

三:剩下60到68,前面6也不贡献了,相当于0到8,就一个。所以加起来就完了。

  注意:万一是168,第一步就不对了,1 * W[2] 后就不能加 100 了,这种情况要注意处理好,下面直接上代码,注释很详细啦:

  1 #include <iostream>
  2 #include <sstream>
  3 #include <string>
  4 #include <cmath>
  5 #include <cstdlib>
  6 #include <fstream>
  7 #include <ctime>
  8 
  9 using std::cout;
 10 using std::endl;
 11 using std::cin;
 12 using std::string;
 13 
 14 //最原始的方法,即从1到n,看每个数字中的“1”的个数,然后加到一起
 15 int getCntSimple(int n)
 16 {
 17     int cnt = 0;
 18     for (int i = 1; i <= n; i++)
 19     {
 20         //数字到字符串的转换
 21         std::stringstream ss;
 22         ss << i;
 23         string temp = ss.str();
 24         
 25         int thisCnt = 0;
 26         for (int j = 0; j < temp.size(); j++)
 27         {
 28             if ('1' == temp[j])
 29                 thisCnt++;
 30         }
 31 
 32         cnt += thisCnt;
 33     }
 34     return cnt;
 35 }
 36 
 37 //较好的方法
 38 enum{N = 5};
 39 int W[N];        //数组含义:下标 i 代表整数位数是 i 的共有包含多少个 1,如 i 是 2 代表[1, 99]区间全部整数共包含多少个 1
 40 
 41 //初始化W数组
 42 void initWArr(void)
 43 {
 44     W[0] = 0;
 45     for (int i = 0; i < N - 1; i++)
 46     {
 47         W[i + 1] = W[i] * 10 + std::pow(10, i);
 48     }
 49 }
 50 
 51 int getCntBetter(int n)
 52 {
 53     //数字转成字符串
 54     std::stringstream ss;
 55     ss << n;
 56     string temp = ss.str();
 57 
 58     int len = temp.size(), res = 0;
 59 
 60     //其实这里的每次循环处理的都是最大的一位,以后的都相当于零头子
 61     for (int i = 0; i < len; i++)
 62     {
 63         int num = (temp[i] - '0');        //由字符转为数字
 64 
 65         if (num > 1)        //注意当前这一位是不是比1大
 66         {
 67             res += num * W[len - i - 1] + std::pow(10, len - i - 1);        //比1大就直接加上10^(len - i - 1)个,完整的那么多次方个,完全贡献
 68         }
 69         else if (0 == num)        //如果这个数是0
 70         {
 71             continue;        //直接进行下一次循环
 72         }
 73         else    //这数字是1,下个循环要去掉这个位,得先把这个1的贡献算上
 74         {
 75             if (i < len - 1)            //不是最后一位的1
 76             {
 77                 //获取这个1后面的数字
 78                 string strVal = temp.substr(i + 1);
 79                 std::stringstream ss(strVal);
 80                 int val;
 81                 ss >> val;
 82 
 83                 res += W[len - i - 1] + (val + 1);
 84             }
 85             else     //最后一位了,且是1
 86             {
 87                 res += 1;
 88             }
 89         }
 90     }
 91 
 92     return res;
 93 }
 94 
 95 int main(void)
 96 {
 97     initWArr();
 98     std::ofstream fout("src.txt");
 99 
100     /*
101     for (int i = 0; i < 2000; i++)
102     {
103         int n =std::rand() % 1000;
104         int a = getCntSimple(n);
105         int b = getCntBetter(n);
106         fout << n << "   :" << a << "  " << b << endl;
107         cout << i << ((a == b) ? " 是" : " 否") << endl;
108     }*/
109 
110     clock_t aTimeStart = std::clock();
111     for (int i = 0; i < 2000; i++)
112     {
113         int a = getCntSimple(i);
114     }
115     clock_t aTimeEnd = std::clock();
116     cout << "runing time a: " << static_cast<double>(aTimeEnd - aTimeStart) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
117     
118     clock_t bTimeStart = std::clock();
119     for (int i = 0; i < 2000; i++)
120     {
121         int a = getCntBetter(i);
122     }
123     clock_t bTimeEnd = std::clock();
124     cout << "runing time b: " << static_cast<double>(bTimeEnd - bTimeStart) / CLOCKS_PER_SEC * 1000 << "ms" << endl;
125 
126     cin.get();
127 }
View Code

  多多注意从第61行开始的判断:

(1)如果这个数字是大于1的,那么就类似于求W数组的那种方式;

(2)这个数字是0,那么直接跳过,没有贡献;

(3)这个数字是1的话,注意,如果这个1已经是最后一位了,那么直接加一个1就好了。但是如果不是的话,假设这是数字是123了,那么想要去掉这个1直接处理后面的零头23时,[1, 99]可以用W[2]获得,但是这个一对后面的24个数字(00到23)都有影响,所以要加上这么多个1。

可以运行试试。

原文地址:https://www.cnblogs.com/jiayith/p/3925393.html