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 ...
随机推荐
- Ajax缓存解决办法
解决办法有如下几种: 1.在服务端加 header("Cache-Control: no-cache, must-revalidate");(如php中) 2.在aja ...
- qregularexpression和qregexp的区别
QRegularExpression 是Qt 5.0才引进的,相对于QRegExp,QRegularExpression class修复了很多bug,提高了效率,提供了对Perl的RegEx几乎全面兼 ...
- 如何保护你的linux操作系统
如何保护你的linux操作系统 导读 在现在这个世道中,Linux操作系统的安全是十分重要的.但是,你得知道怎么干.一个简单反恶意程序软件是远远不够的,你需要采取其它措施来协同工作.那么试试下面这些手 ...
- NSTimer定时器的用法
#import "ViewController.h" @interface ViewController () { NSTimer *countDownTimer; int cou ...
- c++ STL:队列queue、优先队列priority queue 的使用
说明:本文全文转载而来,原文链接:http://www.cppblog.com/wanghaiguang/archive/2012/06/05/177644.html C++ Queues(队列) C ...
- Android开发——实现固定在ScrollView顶部的View,类似于新浪微博的评论列表的顶部
现在很多App都实现了这个功能,例如新浪微博评论页面的评论.转发.赞的数字可以固定在屏幕上方.我个人很喜欢这种设计,所以利用一点空余时间简单实现了一个类似的功能. 先来看一下上面这张图的效果 这个是新 ...
- svn配置
svn配置 subverson.conf <Location /svn> DAV svn SVNListParentPath on SVNParentPath /var/repo Auth ...
- React Native学习-控制横竖屏第三方组件:react-native-orientation
在项目中,有时候可能会想使不同的页面显示的横竖屏也不一样,比如前一段我做的<广播体操>的项目,在首页面,肯定是想使页面为竖屏显示,但是播放页面要为横屏显示,即使用户的手机可以转屏,我们的播 ...
- CF Destroying Roads (最短路)
Destroying Roads time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...
- DWZ框架Ajax无刷新表单提交处理流程
DWZ框架Ajax无刷新表单提交处理流程是: 1. ajax表单提交给服务器 2. 服务器返回一个固定格式json结构 3. js会调函数根据这个json数据做相应 ...