题目:

  输入一个整数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出现的次数的更多相关文章

  1. 【google面试题】求1到n的正数中1出现的次数的两种思路及其复杂度分析

    问题描写叙述: 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数.比如输入12,从1到12这些整数中包括1 的数字有1.10.11和12.1一共出现了5次. 这是一道广为流传的googl ...

  2. 【面试题032】从1到n整数中1出现的次数

    [面试题032]从1到n整数中1出现的次数 题目:     输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数.     例如输入12,从1到12这些整数中包含1的数字有1,10,11和1 ...

  3. 面试题32:从1到n整数中1出现的次数

    这是一道广为流传的google面试题.用最直观的方法求解并不是很难,但遗憾的是效率不是很高:而要得出一个效率较高的算法,需要比较强的分析能力,并不是件很容易的事情.当然,google的面试题中简单的也 ...

  4. 【剑指offer】面试题32:从1到n整数中1出现的次数

    题目: 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1.10.11.12.13因此共出现6次,但是对于后面问题他就没辙了.A ...

  5. 面试题四十三:在1~n整数中1出现的次数

    方法一:直观来看,遍历1到n,每个数去做%10的循环判断 int Number1_B_1toN( int n){ int sum=0; for(int i=1;i<=n;i++){ int k= ...

  6. (剑指Offer)面试题32:从1到n整数中1出现的次数

    题目: 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数.例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,一共出现了5次. 思路: 1.累加法 累加1到n中每个整数 ...

  7. 数组中第K小的数字(Google面试题)

    http://ac.jobdu.com/problem.php?pid=1534 题目1534:数组中第K小的数字 时间限制:2 秒 内存限制:128 兆 特殊判题:否 提交:1120 解决:208 ...

  8. Google 面试题和详解

    Google的面试题在刁钻古怪方面相当出名,甚至已经有些被神化的味道.这个话题已经探讨过很多次,而科技博客 BusinessInsider这两天先是贴出15道Google面试题并一一给出了答案,其中不 ...

  9. Google面试题(选自公众号)

    date: 2018-12-14 13:34:56 updated: 2018-12-14 13:34:56 Google面试题(选自公众号) 问题 把你的手机拨号页想象成一个棋盘.棋子走只能走&qu ...

随机推荐

  1. 文件写入文件分布式系统(asp.net C#)

    ) { try { System.Drawing.Image img = System.Drawing.Image.FromStream(fileData.InputStream); &&am ...

  2. Linux内核分析笔记 与Linux内核开发理论

    http://www.cnblogs.com/hanyan225/category/308793.html

  3. fir.im Weekly - 2016 移动开发技术大回顾

    2016 年是移动技术发展迅速的一年,认认真真回顾这一年必不可少.@移动开发前线 的 这篇 2016移动开发技术巡礼 ,精心盘点了 2016 年 移动开发技术大事件,分为 iOS/Android平台篇 ...

  4. Golang学习 - bytes 包

    ------------------------------------------------------------ 对于传入 []byte 的函数,都不会修改传入的参数,返回值要么是参数的副本, ...

  5. Java微框架:不可忽视的新趋势--转载

    原文:http://www.infoq.com/cn/news/2015/06/Java-Spark-Jodd-Ninja?utm_campaign=infoq_content&utm_sou ...

  6. The Kernel Newbie Corner: Kernel Debugging with proc "Sequence" Files--Part 3

    转载:https://www.linux.com/learn/linux-career-center/44184-the-kernel-newbie-corner-kernel-debugging-w ...

  7. ubuntu 设置IP,设置网关

    1. 检验是否可以连通,就使用ping命令ping 网关 开始的时候总是现实unreachable 2. 设置IP sudo ifconfig eth0 133.133.133.190 netmask ...

  8. iOS企业级开发者计划的申请流程

    第一步:访问苹果企业版iDP网址:https://developer.apple.com/programs/ios/enterprise/点击Apply Now按钮,开始申请流程. 第二步:点击App ...

  9. Python练习题 029:Project Euler 001:3和5的倍数

    开始做 Project Euler 的练习题.网站上总共有565题,真是个大题库啊! # Project Euler, Problem 1: Multiples of 3 and 5 # If we ...

  10. javass 视频笔记二 (关键字,标示符,常量变量,运算符和if-else)

    1,java的关键字和标识符2,java的基本数据类型3,变量和常量4,java的运算符5,if-else if - else表达式1,java的关键字和标识符      1.1,所有关键字都要小写 ...