程序员人生 网站导航

编程之美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

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

最新技术推荐