题目:
输入一个整数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 }
多多注意从第61行开始的判断:
(1)如果这个数字是大于1的,那么就类似于求W数组的那种方式;
(2)这个数字是0,那么直接跳过,没有贡献;
(3)这个数字是1的话,注意,如果这个1已经是最后一位了,那么直接加一个1就好了。但是如果不是的话,假设这是数字是123了,那么想要去掉这个1直接处理后面的零头23时,[1, 99]可以用W[2]获得,但是这个一对后面的24个数字(00到23)都有影响,所以要加上这么多个1。
可以运行试试。