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 = };
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 了,这种情况要注意处理好,下面直接上代码,注释很详细啦:
#include <iostream>
#include <sstream>
#include <string>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <ctime> using std::cout;
using std::endl;
using std::cin;
using std::string; //最原始的方法,即从1到n,看每个数字中的“1”的个数,然后加到一起
int getCntSimple(int n)
{
int cnt = ;
for (int i = ; i <= n; i++)
{
//数字到字符串的转换
std::stringstream ss;
ss << i;
string temp = ss.str(); int thisCnt = ;
for (int j = ; j < temp.size(); j++)
{
if ('' == temp[j])
thisCnt++;
} cnt += thisCnt;
}
return cnt;
} //较好的方法
enum{N = };
int W[N]; //数组含义:下标 i 代表整数位数是 i 的共有包含多少个 1,如 i 是 2 代表[1, 99]区间全部整数共包含多少个 1 //初始化W数组
void initWArr(void)
{
W[] = ;
for (int i = ; i < N - ; i++)
{
W[i + ] = W[i] * + std::pow(, i);
}
} int getCntBetter(int n)
{
//数字转成字符串
std::stringstream ss;
ss << n;
string temp = ss.str(); int len = temp.size(), res = ; //其实这里的每次循环处理的都是最大的一位,以后的都相当于零头子
for (int i = ; i < len; i++)
{
int num = (temp[i] - ''); //由字符转为数字 if (num > ) //注意当前这一位是不是比1大
{
res += num * W[len - i - ] + std::pow(, len - i - ); //比1大就直接加上10^(len - i - 1)个,完整的那么多次方个,完全贡献
}
else if ( == num) //如果这个数是0
{
continue; //直接进行下一次循环
}
else //这数字是1,下个循环要去掉这个位,得先把这个1的贡献算上
{
if (i < len - ) //不是最后一位的1
{
//获取这个1后面的数字
string strVal = temp.substr(i + );
std::stringstream ss(strVal);
int val;
ss >> val; res += W[len - i - ] + (val + );
}
else //最后一位了,且是1
{
res += ;
}
}
} return res;
} int main(void)
{
initWArr();
std::ofstream fout("src.txt"); /*
for (int i = 0; i < 2000; i++)
{
int n =std::rand() % 1000;
int a = getCntSimple(n);
int b = getCntBetter(n);
fout << n << " :" << a << " " << b << endl;
cout << i << ((a == b) ? " 是" : " 否") << endl;
}*/ clock_t aTimeStart = std::clock();
for (int i = ; i < ; i++)
{
int a = getCntSimple(i);
}
clock_t aTimeEnd = std::clock();
cout << "runing time a: " << static_cast<double>(aTimeEnd - aTimeStart) / CLOCKS_PER_SEC * << "ms" << endl; clock_t bTimeStart = std::clock();
for (int i = ; i < ; i++)
{
int a = getCntBetter(i);
}
clock_t bTimeEnd = std::clock();
cout << "runing time b: " << static_cast<double>(bTimeEnd - bTimeStart) / CLOCKS_PER_SEC * << "ms" << endl; cin.get();
}
多多注意从第61行开始的判断:
(1)如果这个数字是大于1的,那么就类似于求W数组的那种方式;
(2)这个数字是0,那么直接跳过,没有贡献;
(3)这个数字是1的话,注意,如果这个1已经是最后一位了,那么直接加一个1就好了。但是如果不是的话,假设这是数字是123了,那么想要去掉这个1直接处理后面的零头23时,[1, 99]可以用W[2]获得,但是这个一对后面的24个数字(00到23)都有影响,所以要加上这么多个1。
可以运行试试。
Google面试题:计算从1到n的正数中1出现的次数的更多相关文章
- 【google面试题】求1到n的正数中1出现的次数的两种思路及其复杂度分析
问题描写叙述: 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数.比如输入12,从1到12这些整数中包括1 的数字有1.10.11和12.1一共出现了5次. 这是一道广为流传的googl ...
- 【面试题032】从1到n整数中1出现的次数
[面试题032]从1到n整数中1出现的次数 题目: 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数. 例如输入12,从1到12这些整数中包含1的数字有1,10,11和1 ...
- 面试题32:从1到n整数中1出现的次数
这是一道广为流传的google面试题.用最直观的方法求解并不是很难,但遗憾的是效率不是很高:而要得出一个效率较高的算法,需要比较强的分析能力,并不是件很容易的事情.当然,google的面试题中简单的也 ...
- 【剑指offer】面试题32:从1到n整数中1出现的次数
题目: 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1.10.11.12.13因此共出现6次,但是对于后面问题他就没辙了.A ...
- 面试题四十三:在1~n整数中1出现的次数
方法一:直观来看,遍历1到n,每个数去做%10的循环判断 int Number1_B_1toN( int n){ int sum=0; for(int i=1;i<=n;i++){ int k= ...
- (剑指Offer)面试题32:从1到n整数中1出现的次数
题目: 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数.例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,一共出现了5次. 思路: 1.累加法 累加1到n中每个整数 ...
- 数组中第K小的数字(Google面试题)
http://ac.jobdu.com/problem.php?pid=1534 题目1534:数组中第K小的数字 时间限制:2 秒 内存限制:128 兆 特殊判题:否 提交:1120 解决:208 ...
- Google 面试题和详解
Google的面试题在刁钻古怪方面相当出名,甚至已经有些被神化的味道.这个话题已经探讨过很多次,而科技博客 BusinessInsider这两天先是贴出15道Google面试题并一一给出了答案,其中不 ...
- Google面试题(选自公众号)
date: 2018-12-14 13:34:56 updated: 2018-12-14 13:34:56 Google面试题(选自公众号) 问题 把你的手机拨号页想象成一个棋盘.棋子走只能走&qu ...
随机推荐
- 剑指 offer set 4 矩形覆盖
总结 1. 斐波那契数列的变形题, 但是稍有隐晦, 有点意思 2. 求解 f(3) 时, 最后一块矩形可以竖着放, 也可以两块矩形横着放, 分别对应于 f(2) 和 f(1) ------------ ...
- 如何使用strace+pstack利器分析程序性能
http://www.cnblogs.com/bangerlee/archive/2012/04/30/2476190.html
- 四元数(Quaternion)和旋转
四元数介绍 旋转,应该是三种坐标变换——缩放.旋转和平移,中最复杂的一种了.大家应该都听过,有一种旋转的表示方法叫四元数.按照我们的习惯,我们更加熟悉的是另外两种旋转的表示方法——矩阵旋转和欧拉旋转. ...
- ubuntu 设置IP,设置网关
1. 检验是否可以连通,就使用ping命令ping 网关 开始的时候总是现实unreachable 2. 设置IP sudo ifconfig eth0 133.133.133.190 netmask ...
- C# 之 反射
一.反射的定义: 审查元数据并收集关於它的类型信息的能力,元数据(编辑后的基本数据单元)就是一大堆表,编译器会创建一个类定义表,一个字段定义表,一个方法定义表等, System.Reflection ...
- Python操作MySQL之SQLAlchemy
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结 ...
- 二手奢侈品电商Vestiaire Collective融资2000万美元
巴黎奢侈品电商Vestiaire Collective获得了2000万美元的C轮融资,投资方包括知名出版集团Condé Nast.Idinvest.Balderton和Ventech,其中Condé ...
- 【Python Lib】解析HTML利器 BeautifulSoup
- - 官方API文档,中文版 http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html - 以后会把常用的摘录出来
- 九 AIDL
多进程 多线程 优点 安全 稳定 扩大内存空间 节约CPU时间 AIDL=Android Interface definition language 使用情况:做一个下载,不想让你的下载拖垮你 ...
- js实现hashtable的赋值、取值、遍历
哈希表(Hashtable)这个概率应该是#c里面的概念,用来赋值.取值.遍历.排序操作提高效率.想起这个东西其实使我们以前经常遇到这样的面试题,一个很大的数组可能有100000个,如何快速知道它里面 ...