程序员人生 网站导航

编程之美1:那些关于1的个数的经典面试题

栏目:综合技术时间:2015-04-28 08:39:46

那些关于1的个数的经典面试题

好长时间没有练算法了,笔试题1做,发现非常费劲,所以近日来找来《编程之美》1书来看看练练。为了鼓励自己多练,楼楼可能会出个专栏甚么的,感兴趣的同学我们可以1起抱团,楼楼也会保证每天都会更新。那今天呢,就是《编程之美》的第1题了,原题叫做“1”的数目,楼楼会把这道题还有相干的1些题都会记录下来,下面要开始了哦,Are you ready?

题目1 给定1个10进制正整数N,写下从1开始,到N的所有整数,然后数1下其中出现的所有“1”的个数

  • 解法1 暴力穷举

我相信如果是我们正在处于笔试或面试确当场,暴力穷举肯定是我们的第1个想法。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; }

我们分析1下复杂度,外层循环要循环N次,内存循环要循环log10N+1次,所以总的复杂度为O(N(log10N+1)),可以看出这个复杂度是比较高的。

下面我们想一想,有无更简单的办法呢?比如对1个3位数123而言,“1”只能在个位出现,或10位出现或千位出现。如果是依照这个原理来统计的,那我们可以完全将外层循环下降到log10N+1次。那我们来写几个例子来寻觅1下规律。

  • 解法2 逐位统计法

如123,那末

个位出现1 10位出现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样的。还有相应的位数为1怎样办?12和22的结果也是不1样的。我下面把结果罗列出来,大家也能够试着推导1下。

分情况 个位出现1 10位出现1 百位出现1
bit = 0 (N/10)?1 (N/100)?10 (N/1000)?100
bit = 1 (N/10)?1+N%1+1 (N/100)?10+N%10+1 (N/1000)?100+N%100+1
bit > 1 (N/10+1)?1 (N/100+1)?10 (N/1000+1)?100

总结1下,假定每位对应的权值为quan,如个位quan = 1,10位quan = 10,那末总的公式为

bit=0 bit=1 bit>1
(N/(10?quan))?quan (N/(10?quan))?quan+N%quan+1 (N/(10?quan)+1)?quan

下面看代码:

package net.mindview.util; public class MyThread { public static void main(String[] args) { int N = 123; System.out.println("总共出现" + getOneShowTimes(N) + "次"); } public static int getOneShowTimes(int N) { int numPerBit; //存储每位的数目 int sumTimes = 0; //存储最后的结果 int quan = 1; //每位的权值,各位为1,10位为10,顺次类推 int tempN = N; if (0 == N) { return 0; } while(0 != tempN) { numPerBit = tempN % 10; sumTimes += getOneShowTimesPerBit(N, numPerBit, quan); tempN /= 10; quan *= 10; } return sumTimes; } public static int getOneShowTimesPerBit(int N, int numPerBit, int quan) { if (0 == numPerBit) { return N / (quan * 10) * quan; } else if (1 == numPerBit) { return (N / (quan * 10)) * quan + N % quan + 1; } else { return (N / (quan * 10) + 1 ) * quan; } } }

非常不好意思,这里的代码是Java的,由于原来就写好了,我就懒得再写成c++的了,请见谅哈!复杂度为O(log10N+1),可以看出来效力是提升了很多的。下面我们来看第2个题。

题目2:给定1个10进制正整数N,写下从1开始,到N的所有整数,然后数1下其中2进制中出现的所有“1”的个数

题目1是10进制,题目2是2进制。注意这里的区分。那我们一样写写看,看能不能找出甚么规律

假定N=3,那末我们看每位出现“1”的次数之和都是相等的,都是2次。那末结果就是2 * 2=4总共4次。其次,假定N=7,我们又惊奇地发现,所有小于7的数中每位出现“1”的次数之和也是相等的,每位出现“1”的次数之和为4,那末结果就是3 * 4 = 12次。那我们可以料想,当N=15时,总数为4 * 8 = 32次。这是非常理想的情况,那当N = 13呢?很easy,N=13,我们就先算N = 7。还剩下6个数,视察1下我们可以发现,剩下的6个数中“1”出现的次数=最小的6个数“1”出现的次数+6。那末我们就把问题降下来了,本来是要求左侧6个数中1出现的个数,转变成了求右侧6个数中1出现的个数。

本来要求的“1”出现的个数 简化以后要求的“1”出现的个数
1000 0000
1001 0001
1010 0010
1011 0011
1100 0100
1101 0101

那末剩下的6个数,又可以重复前面的步骤,先求N=3。
总结1下,假定不大于N的最小的2的次方数为biggest2Pow
log2(biggest2Pow)?biggest2Pow2+(N+1

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐