那些关于1的个数的经典面试题
好长时间没有练算法了,笔试题一做,发现非常吃力,所以近日来找来《编程之美》一书来看看练练。为了激励自己多练,楼楼可能会出个专栏什么的,感兴趣的同学我们可以一起抱团,楼楼也会保证每天都会更新。那今天呢,就是《编程之美》的第一题了,原题叫做“1”的数目,楼楼会把这道题还有相关的一些题都会记录下来,下面要开始了哦,Are you ready?
题目1 给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有“1”的个数
- 解法1 暴力穷举
我相信如果是我们正在处于笔试或者面试的当场,暴力穷举肯定是我们的第一个想法。一个一个算,算出1中出现“1”的个数,再算出2中出现“1”的个数,依次类推,直到N中“1”的个数,然后相加得出最后的结论。我们看代码:
#include <iostream>
using namespace std;
int getOneShowTimes(unsigned int n) ;
int getOneShowSumTimes(unsigned int N) ;
int main()
{
unsigned int N = 123;
cout << "1出现的次数为:" << getOneShowSumTimes(N) << endl;
system("pause");
}
int getOneShowSumTimes(unsigned int N)
{
unsigned int count = 0;
for (unsigned int i = 0; i <= N; i++)
{
count += getOneShowTimes(i);
}
return count;
}
int getOneShowTimes(unsigned int n)
{
unsigned count = 0;
while(0 != n)
{
count += (n % 10) == 1 ? 1 : 0;
n /= 10;
}
return count;
}
我们分析一下复杂度,外层循环要循环N次,内存循环要循环 log10N+1 次,所以总的复杂度为 O(N(log10N+1)) ,可以看出这个复杂度是比较高的。
下面我们想想,有没有更简单的办法呢?比如对于一个三位数123而言,“1”只能在个位出现,或者十位出现或者千位出现。如果是按照这个原理来统计的,那我们可以完全将外层循环降低到 log10N+1 次。那我们来写几个例子来寻找一下规律。
- 解法2 逐位统计法
如123,那么
个位出现1 | 十位出现1 | 百位出现1 |
---|---|---|
1 | 10 | 100 |
11 | 11 | 101 |
21 | 12 | 102 |
31 | 13 | 103 |
41 | 14 | 104 |
51 | 15 | … |
61 | 16 | |
71 | 17 | |
81 | 18 | |
91 | 19 | |
101 | 110 | |
111 | … | |
121 | 119 | 123 |
共计13次 | 共计20次 | 共计24次 |
猜想公式 N/10+1 | 猜想公式 (N/100+1)∗10 | 猜想公式 (N/1000)∗100+N%100+1 |
但是在这里有我们有几个特殊的情况需要特别考虑,如相应的位数为0怎么办?比如51和50结果是完全不一样的。还有相应的位数为1怎么办?12和22的结果也是不一样的。我下面把结果罗列出来,大家也可以试着推导一下。
分情况 | 个位出现1 | 十位出现1 | 百位出现1 |
---|---|---|---|
bit = 0 |