代码改变世界

面试题

2012-03-25 18:11  Rollen Holt  阅读(2348)  评论(0编辑  收藏  举报

微软面试题:地球上有多少个满足这样条件的点

站在地球上的某一点,向南走一公里,然后向东走一公里,最后向北走一公里,回到了原点。地球上有多少个满足这样条件的点?

北极点满足这个条件。

距离南极点很近的一个圈上也满足这个条件。在这个圆圈上,向南走一公里,然后向东走一公里恰好绕南极点一圈,向北走一公里回到原点。

所以地球上总共有无数点满足这个条件。

谷歌面试题:判断一个自然数是否是某个数的平方

判断一个自然数是否是某个数的平方。当然不能使用开方运算。

假设待判断的数字是 N。

 

方法1:

遍历从1到N的数字,求取平方并和N进行比较。

如果平方小于N,则继续遍历;如果等于N,则成功退出;如果大于N,则失败退出。

复杂度为O(n^0.5)。

 

方法2:

使用二分查找法,对1到N之间的数字进行判断。

复杂度为O(log n)。

 

方法3:

由于

(n+1)^2

=n^2 + 2n + 1,

= ...

= 1 + (2*1 + 1) + (2*2 + 1) + ... + (2*n + 1)

注意到这些项构成了等差数列(每项之间相差2)。

所以我们可以比较 N-1, N - 1 - 3, N - 1 - 3 - 5 ... 和0的关系。

如果大于0,则继续减;如果等于0,则成功退出;如果小于 0,则失败退出。

复杂度为O(n^0.5)。不过方法3中利用加减法替换掉了方法1中的乘法,所以速度会更快些。

 

谷歌面试题:如何随机选取1000个关键字

给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字)。如何才能从这个无穷尽的流中随机的选取1000个关键字?

定义长度为1000的数组。

对于数据流中的前1000个关键字,显然都要放到数组中。

对于数据流中的的第n(n>1000)个关键字,我们知道这个关键字被随机选中的概率为 1000/n。所以我们以 1000/n 的概率用这个关键字去替换数组中的随机一个。这样就可以保证所有关键字都以 1000/n的概率被选中。

对于后面的关键字都进行这样的处理,这样我们就可以保证数组中总是保存着1000个随机关键字。

 

谷歌面试题:将下列表达式按照复杂度排序

将下列表达式按照复杂度排序

 

2^n

n^Googol (其中 Googol = 10^100)

n!

n^n

:

按照复杂度从低到高为

n^Googol

2^n

n!

n^n

 

微软面试题:正确标注水果篮

有三个水果篮。其中一个里面只有苹果,一个里面只有橘子,另外一个既有苹果又有橘子。每个水果篮上都有标签,但标签都是错的。如何检查某个水果篮中的一个水果,然后正确标注每个水果篮?

从标注成既有苹果也有橘子的水果篮中选取一个进行检查。

如果是橘子,则此篮中只有橘子;标有橘子的水果篮中只有苹果;标有苹果的水果篮中既有苹果也有橘子。

如果是苹果,则此篮中只有苹果;标有苹果的水果篮中只有橘子;标有橘子的水果篮中既有苹果也有橘子。

 

IBM面试题:为什么小和尚会在同一时间出现在同一地点

有一座山,山上有座庙,只有一条路可以从山上的庙到山脚,每周一早上8点,有一个聪明的小和尚去山下化缘,周二早上8点从山脚回山上的庙里,小和尚的上下山的速度是任意的,在每个往返中,他总是能在周一和周二的同一钟点到达山路上的同一点。例如,有一次他发现星期一的8点30和星期二的8点30他都到了山路靠山脚的3/4的地方,问这是为什么?

可以用画图法来解释:

在一个平面上,x 轴代表从8点开始的时间,y 轴代表距庙的距离。那么从庙到山脚就是一条从左下到右上的一条曲线,从山脚到庙就是一条从左上到右下的一条曲线。考虑到两条曲线的起始点和终点,两线必定交于一点。

 

微软面试题:不利用浮点运算,画一个圆

不利用浮点运算,在屏幕上画一个圆 (x**2 + y**2 = r**2,其中 r 为正整数)。

考虑到圆的对称性,我们只需考虑第一象限即可。

等价于找到一条连接点(0,r)到点(r,0)的一条曲线,曲线上的点距圆心(0,0)的距离最接近 r。

我们可以从点(0,r)开始,搜索右(1,r),下(0,r-1),右下(1,r-1)三个点到圆心的距离,选择距圆心距离最接近 r 的点作为下一个点。反复进行这种运算,直至到达点(r,0)。

由于不能利用浮点运算,所以距离的比较只能在距离平方的基础上进行。也就是比较 x**2 + y**2 和 r**2之间的差值。

 

微软面试题:计算n bit的整数中有多少bit 为1

设此整数为x。

 

方法1:

让此整数除以2,如果余数为1,说明最后一位是1,统计值加1。

将除得的结果进行上面运算,直到结果为0。

 

方法2:

考虑除法复杂度有些高,可以使用移位操作代替除法。

将 x 和 1 进行按位与操作(x&1),如果结果为1,说明最后一位是1,统计值加1。

将x 向右一位(x >> 1),重复上面过程,直到移位后结果为0。

 

方法3:

如果需要统计很多数字,并且内存足够大,可以考虑将每个数对应的bit为1的数量记录下来,这样每次计算只是一次查找操作。

 

微软面试题:快速求取一个整数的7倍

乘法相对比较慢,所以快速的方法就是将这个乘法转换成加减法和移位操作。

可以将此整数先左移三位(×8)然后再减去原值:X << 3 - X。

 

微软面试题:判断一个数是不是2的n次幂

设要判断的数是无符号整数X。

首先判断X是否为0,如果为0则不是2的n次幂,返回。

X和X-1进行按位与操作,如果结果是0,则说明这个数是2的n次幂;如果结果非0,则说明这个数不是2 的n次幂。

 

证明:

如果是2的n次幂,则此数用二进制表示时只有一位是1,其它都是0。减1后,此位变成0,后面的位变成1,所以按位与后结果是0。

如果不是2的n次幂,则此数用二进制表示时有多位是1。减1后,只有最后一个1变成0,前面的 1还是1,所以按位与后结果不是0。

微软面试题:三只蚂蚁不相撞的概率是多少

在三角形的三个顶点上各有一只蚂蚁,它们向另一个顶点运动,目标随机(可能为另外两个顶点的任意一个)。问三只蚂蚁不相撞的概率是多少?

如果蚂蚁顺时针爬行记为0,逆时针爬行记为1。那么三只蚂蚁的状态可能为000,001,...,110,111中的任意一个,且为每种状态的概率相等。在这8种状态中,只有000和111可以避免相撞,所以蚂蚁不相撞的概率是1/4。

 

题目的一些扩展:

1. 如果将三角形变成正方形,并且有四只蚂蚁,那么不相撞的概率是多少?(1/8)

2. 如果将正方形的对角线相连,蚂蚁也可以在对角线上爬行,那么不相撞的概率是多少?(假设对角线相交处有立交桥)(暂无答案)

扩展2的答案好像是 6/ 81。

由于每只蚂蚁都可以向其它三个顶点移动,所以总的可能情况为3*3*3*3=81。

所有蚂蚁顺时针或者逆时针可以避免相撞,这有两种情况。

如果只有一只蚂蚁顺着对角线移动,是无法避免相撞的。

两只蚂蚁顺着对角线移动可以避免相撞,总共有4种情况。

所以总的避免相撞的情况有6 种。

题目还可以进一步扩展:

将正方形换成正n边形,每两个顶点都相互链接。(暂无答案)

 

微软面试题:判断数组中是否包含重复数字

给定一个长度为N的数组,其中每个元素的取值范围都是1到N。判断数组中是否有重复的数字。(原数组不必保留)

 

方法1.

对数组进行排序(快速,堆),然后比较相邻的元素是否相同。

时间复杂度为O(nlogn),空间复杂度为O(1)。

 

方法2.

使用bitmap方法。

定义长度为N/8的char数组,每个bit表示对应数字是否出现过。遍历数组,使用 bitmap对数字是否出现进行统计。

时间复杂度为O(n),空间复杂度为O(n)。

 

方法3.

遍历数组,假设第 i 个位置的数字为 j ,则通过交换将 j 换到下标为 j 的位置上。直到所有数字都出现在自己对应的下标处,或发生了冲突。

时间复杂度为O(n),空间复杂度为O(1)。

 

微软面试题:如何将蛋糕切成相等的两份

一块长方形的蛋糕,其中有一个小长方形的空洞(角度任意)。使用一把直刀,如何一刀将蛋糕切成相等的两份?

通过长方形中心的的任意直线都能将长方形等分,所以连接两个长方形的中心点的直线可以等分这个蛋糕。

 

谷歌面试题:在半径为1的圆中随机选取一点

在半径为1的圆中随机选取一点。

假设圆心所在位置为坐标元点(0, 0)。

 

方法1.

在x轴[-1, 1],y轴[-1, 1]的正方形内随机选取一点。然后判断此点是否在圆内(通过计算此点到圆心的距离)。如果在圆内,则此点即为所求;如果不在,则重新选取直到找到为止。

正方形的面积为4,圆的面积为pi,所以正方形内的随机点在圆内的概率是 pi / 4。

 

方法2.

从[0, 2*pi)中随机选一个角度,对应于圆中的一条半径,然后在此半径上选一个点。但半径上的点不能均匀选取,选取的概率应该和距圆心的长度成正比,这样才能保证随机点在圆内是均匀分布的。

 

谷歌面试题:给定一个未知长度的整数流,如何随机选取一个数

给定一个未知长度的整数流,如何随机选取一个数?

方法1.

将整个整数流保存到一个数组中,然后再随机选取。

如果整数流很长,无法保存下来,则此方法不能使用。

 

方法2.

如果整数流在第一个数后结束,则我们必定会选第一个数作为随机数。

如果整数流在第二个数后结束,我们选第二个数的概率为1/2。我们以1/2的概率用第2个数替换前面选的随机数,得到满足条件的新随机数。

....

如果整数流在第n个数后结束,我们选第n个数的概率为1/n。我们以1/n的概率用第n个数替换前面选的随机数,得到满足条件的新随机数。

....

利用这种方法,我们只需保存一个随机数,和迄今整数流的长度即可。所以可以处理任意长的整数流。

 

谷歌面试题:设计方便提取中数的数据结构

设计一个数据结构,其中包含两个函数,1.插入一个数字,2.获得中数。并估计时间复杂度。

1. 使用数组存储。

插入数字时,在O(1)时间内将该数字插入到数组最后。

获取中数时,在O(n)时间内找到中数。(选数组的第一个数和其它数比较,并根据比较结果的大小分成两组,那么我们可以确定中数在哪组中。然后对那一组按照同样的方法进一步细分,直到找到中数。)

 

2. 使用排序数组存储。

插入数字时,在O(logn)时间内找到要插入的位置,在O(n)时间里移动元素并将新数字插入到合适的位置。

获得中数时,在O(1)复杂度内找到中数。

 

3. 使用大根堆和小根堆存储。

使用大根堆存储较小的一半数字,使用小根堆存储较大的一半数字。

插入数字时,在O(logn)时间内将该数字插入到对应的堆当中,并适当移动根节点以保持两个堆数字相等(或相差1)。

获取中数时,在O(1)时间内找到中数。

 

谷歌面试题:在一个特殊数组中进行查找

给定一个固定长度的数组,将递增整数序列写入这个数组。当写到数组尾部时,返回数组开始重新写,并覆盖先前写过的数。

请在这个特殊数组中找出给定的整数。

假设数组为a[0, 1, ..., N-1]。

我们可以采用类似二分查找的策略。

首先比较a[0]和a[N/2],如果a[0] < a[N/2],则说明a[0,1,...,N/2]为递增子序列,否则另一部分是递增子序列。

然后判断要找的整数是否在递增子序列范围内。如果在,则使用普通的二分查找方法继续查找;如果不在,则重复上面的查找过程,直到找到或者失败为止。

谷歌面试题:给定两个已排序序列,找出共同的元素

不妨假设序列是从小到大排序的。定义两个指针分别指向序列的开始。

如果指向的两个元素相等,则找到一个相同的元素;如果不等,则将指向较小元素的指针向前移动。

重复执行上面的步骤,直到有一个指针指向序列尾端。

 

谷歌面试题:找到链表的倒数第m个节点

方法1:

首先遍历链表,统计链表的长度N。

然后再次遍历链表,找到第N-m个节点,即为倒数第m个节点。

 

方法2:

使用两个指针,并使它们指向的节点相距m-1个。

然后同时向前移动两个指针,当一个指针指最后一个节点时,第二个指针指向倒数第m个节点。

 

两个方法的复杂度都是O(n)。

但是当N较大而m较小时,方法2可能会更快一些。因为方法2能更好利用CPU的缓存。

 

更多阅读:

http://baike.baidu.com/view/2089.htm CPU -> 缓存

 

谷歌面试题:给定一个排序数组,如何构造一个二叉排序树?

采用递归算法。

选取数组中间的一个元素作为根节点,左边的元素构造左子树,右边的节点构造有子树。

 

谷歌面试题:数组中是否有两个数的和为10

1.

比较任意两个数的和是否为10。如

for (int i = 0; i < n; ++i) { for (int j = i+1; j < n; ++j) { .... }}

复杂度为O(n*n)。

 

2.

将数组排序后,对每个数m,使用二分查找在数组中寻找10-m。

复杂度为O(nlogn)。

 

3.

将数组存储到hash_set中去,对每个数m,在hash_set中寻找10-m。

复杂度为O(n)。

 

4.

如果数组很大,超过内存的容量,可以按照hash(max(m, 10-m))%g,将数据分到g个小的group中。然后对每个小的group进行单独处理。

复杂度为O(n)。

 

百度面试题:三个警察和三个囚徒的过河问题

三个警察和三个囚徒共同旅行。一条河挡住了去路,河边有一条船,但是每次只能载2人。存在如下的危险:无论在河的哪边,当囚徒人数多于警察的人数时,将有警察被囚徒杀死。

问题:请问如何确定渡河方案,才能保证6人安全无损的过河。

警察囚徒过去,警察回来

囚徒囚徒过去,囚徒回来

警察警察过去,警察囚徒回来

警察警察过去,囚徒回来

囚徒囚徒过去,囚徒回来

囚徒囚徒过去

 

雅虎面试题:HTTP中Get和Post的区别

 

Get和Post都是浏览器向网页服务器提交数据的方法。

 

Get把要提交的数据编码在url中,比如 http://hi.baidu.com/mianshiti?key1=value1&key2=value2 中就编码了键值对 key1,value1 和key2,value2。受限于url的长度限制,Get方法能传输的数据有限(不同浏览器对url长度限制不同,比如微软IE设为2048)。

Post把要提交的数据放在请求的body中,而不会显示在url中,因此,也没有数据大小的限制。

 

由于Get把数据编码在URL中,所以这些变量显示在浏览器的地址栏,也会被记录在服务器端的日志中。所以Post方法更加安全。

 

雅虎面试题:灯炮能亮的概率是多少?

一个开关接一个灯炮,开关一年内坏掉的概率为10%,灯炮一年内坏掉的概率为20%,问一年后打开开关,灯炮能亮的概率是多少?(假定其他设备都不损坏)

开关好的概率是90%,灯泡好的概率是80%,

所以两个都好的概率是90%*80%=72%。

 

华为面试题:IP,TCP和UDP协议的定义和主要作用

 

IP协议是网络层的协议。IP协议规定每个互联网网上的电脑都有一个唯一的IP地址,这样数据包就可以通过路由器的转发到达指定的电脑。但IP协议并不保证数据传输的可靠性。

TCP协议是传输层的协议。它向下屏蔽了IP协议不能可靠传输的缺点,向上提供面向连接的可靠的数据传输。

UDP协议也是传输层的协议。它提供无连接的不可靠传输。

 

华为面试题:全局变量和局部变量有什么区别?

全局变量是整个程序都可访问的变量,生存期从程序开始到程序结束;局部变量存在于模块中(比如某个函数),只有在模块中才可以访问,生存期从模块开始到模块结束。

全局变量分配在全局数据段,在程序开始运行的时候被加载。局部变量则分配在程序的堆栈中。因此,操作系统和编译器可以通过内存分配的位置来知道来区分全局变量和局部变量。

 

华为面试题:析构函数和虚函数的用法和作用?

析构函数是在类对象消亡时由系统自动调用。主要用来做对象的清理工作,比如来释放对象申请的动态空间。

基类中用virtual修饰的函数称为虚函数。在派生类中可以对虚函数进行重新定义,这样同样的函数接口可以在不同的派生类中对应不同的实现。当通过基类的指针来调用虚函数时,程序会根据指针实际指向的对象来决定调用哪个实现。

 

华为面试题:6个整数,每个数中都包含'6',和为100可能吗?

6个整数,每个数中都包含'6'(在个位或十位上),6个数的和为100可能吗?

可能,这六个数的取值为:60,16,6,6,6,6

首先,6不可能都在个位数上出现,否则6个数的和个位也是会是6。

其次,最多有一个数在十位上出现6,否则6个数的和会大于100。

这六个数为 6?,?6,?6,?6,?6,?6。不考虑?部分,总和等于90。所以最终的结果60,16,6,6,6,6。

 

 

华为面试题:找出字符串中的数字和字母,字母要求大写输出

首先定义一个长度为256的字符数组char_map,将数组的值设为:

char_map['0'] = '0';

....

char_map['9'] = '9';

char_map['A'] = 'A';

....

char_map['Z'] = 'Z';

char_map['a'] = 'A';

....

char_map['z'] = 'Z';

其它值设为0。

 

然后遍历字符串中的所有字符,使用此字符作为下标到char_map中找出对应的值。如果值为0,则不输出,如果不为0,则输出查找到的值。

完美时空面试题:memcpy 和 memmove 有什么区别?

memcpy和memmove都是将源地址的若干个字符拷贝到目标地址。

如果源地址和目标地址有重叠,则memcpy不能保证拷贝正确,但memmove可以保证拷贝正确。

 

例如:

char src[20];

// set src

char* dst = src + 5;

此时如果要从src拷贝10个字符到dst,则么memcpy不能保证拷贝正确,但是memmove可以保证。

 

腾讯面试题:const的含义及实现机制

const用来说明所定义的变量是只读的。

这些在编译期间完成,编译器可能使用常数直接替换掉对此变量的引用。

 

迅雷面试题:门面模式的解释、适用场合?

门面模式又被称为外观模式,为子系统中的一组接口提供一个一致的界面,该模式定义了一个高层接口,使得这个子系统更加容易使用。

举个例子:在做项目或产品的过程中进行跨部门合作的时候,每个部门都有个相应的接口人,那么我们只需和对应部门的接口人交互即可。

 

适用场合:

为一个复杂子系统提供一个简单接口:子系统往往因为不断演化而变得越来越复杂,使用门面模式可以使得子系统更具有可复用性。

子系统的独立性:引入门面模式将一个子系统与它的客户端以及其他子系统分离,可以提高子系统的独立性和可移植性。

层次化结构:在构建一个层次化的系统时,可以使用 门面模式定义系统中每一层的入口。如果层与层之间是相互依赖的,则可以限定它们仅通过门面进行通信,简化层与层之间的依赖关系。

 

迅雷面试题:两整数相除,求循环节

求循环节,若整除则返回NULL,否则返回char*指向循环节。先写思路。

函数原型:char* get_circle_digits(unsigned k,unsigned j)

 

回想我们使用手算时如何发现循环节:
- 如果除得的余数等于0,则说明整除;
- 如果除得的余数不等于0,则将余数乘以10,继续相除;
直到发现两次获得的余数相等,则找到了循环节。

 

迅雷面试题:AJAX的原理、如何实现刷新及其优点

AJAX即“Asynchronous JavaScript and XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。

 

使用了AJAX技术的网页,利用Javascript和服务器通信,获取数据,然后再通过修改网页的DOM中的某些元素来实现刷新网页的特定部分。

 

使用了AJAX技术后,由于只需要更新网页的一部分,而不是全部,所以和服务器交互的数据比较少。这就降低了服务器的负载,并提高了用户端的响应速度。另外,AJAX并不需要在浏览器中安装插件。

 

百度面试题:设计DNS服务器中cache的数据结构

要求设计一个DNS的Cache结构,要求能够满足每秒5000以上的查询,满足IP数据的快速插入,查询的速度要快。(题目还给出了一系列的数据,比如:站点数总共为5000万,IP地址有1000万,等等)

DNS服务器实现域名到IP地址的转换。

 

每个域名的平均长度为25个字节(估计值),每个IP为4个字节,所以Cache的每个条目需要大概30个字节。

总共50M个条目,所以需要1.5G个字节的空间。可以放置在内存中。(考虑到每秒5000次操作的限制,也只能放在内存中。)

 

可以考虑的数据结构包括hash_map,字典树,红黑树等等。

这道题目是系统设计题,应该不是这么简单的,比如如果查询包含了许多该DNS不包含的url,如何快速的排除掉,hash查找的效率在这里应该要仔细考虑。 IP地址的快速插入实际上要考虑多线程的问题,隐藏了读写锁的问题在里面。

 

腾讯面试题:买200返100优惠券,实际上折扣是多少?

到商店里买200的商品返还100优惠券(可以在本商店代替现金)。请问实际上折扣是多少?

由于优惠券可以代替现金,所以可以使用200元优惠券买东西,然后还可以获得100元的优惠券。

假设开始时花了x元,那么可以买到 x + x/2 + x/4 + ...的东西。所以实际上折扣是50%.(当然,大部分时候很难一直兑换下去,所以50%是折扣的上限)

 

如果使用优惠券买东西不能获得新的优惠券,那么

总过花去了200元,可以买到200+100元的商品,所以实际折扣为 200/300 = 67%.

 

迅雷面试题:数组与链表的区别?

在数组中,元素在内存中连续存放。对于访问操作,由于元素类型相同,占用内存相同,所以可以通过数组的下标计算出元素所在的内存地址,便于快速访问。但对于插入或删除操作,需要移动大量元素,所以速度比较慢。

在链表中,元素在内存中没有连续存放,而是通过元素中的指针将各个元素连接在一起。对于访问操作,需要从链表头部开始顺序遍历链表,直到找到需要的元素,所以速度比较慢。对于插入或删除操作,只需修改元素中的指针即可完成,速度比较快。

所以,如果需要频繁访问数据,很少插入删除操作,则使用数组;反之,如果频繁插入删除,则应使用链表。

IBM面试题:平面上画1999条直线,最多能将平面分成多少部分?

没有直线时有一个空间;(1)

1条直线时,这条这些可以将这个空间分成两个;(1+1)

2条直线时,第二条直线可以和第一条直线相交,这样第二条直线可以将两个空间分成四个;(1+1+2)

....

注意到画每条直线时能增加多少个空间,取决于此直线从多少个空间中通过。

而从多少个空间中通过,取决于和多少条直线相交。

例如,如果一条直线和其它5条直线相交,那么最大可以通过6个空间,此直线可以增加6个子空间。

画每条直线时,能相交的直线数为总的已经画过的直线。

 

所以总的空间数最多为

1+1+2+3+...+1999 = 1999001

 

IBM面试题:使用两根烧1小时的香,确定15分钟的时间

第一根点燃两头,第二根只点一头。

当第一根烧完时,时间过去了30分钟,所以第二根还能烧30分钟。这时点燃第二根的另外一头,第二根香还能烧的时间就是15分钟。

IBM面试题:27个人去买矿泉水

有27个人去买矿泉水,商店正好在搞三个空矿泉水瓶可以换一瓶矿泉水的活动,他们至少要买几瓶矿泉水才能每人喝到一瓶矿泉水?

 

如果开始买3瓶,那么可以四个人喝,并且还能剩一个空瓶。

如果开始买9瓶,可以13个人喝,最后还剩一个空瓶。

如果开始买18瓶,那么26个人喝,可以剩下两个空瓶。

如果开始买19瓶,那么27个人喝,最后剩下三个空瓶。所以最少买19瓶。

 

如果可以向商店先欲借一个空瓶,那么买18瓶,最后一个人喝完再将空瓶还给商店。

那么买18瓶也可以满足要求。

 

IBM面试题:c++中引用和指针有什么不同?指针加上什么限制等于引用?

引用不是一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。引用一经确定就不能修改。

指针是一个变量,需要在内存中分配空间,此空间中存储所指对象的地址。由于指针是一个普通变量,所以其值还可以通过重新赋值来改变。

把指针定义为const后,其值就不能改变了,功能和引用类似,但有本质的区别。

 

百度面试题:将多个集合合并成没有交集的集合

给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh},{eee fff}, {ggg}。

(1)请描述你解决这个问题的思路;

(2)请给出主要的处理流程,算法,以及算法的复杂度

(3)请描述可能的改进。

集合使用hash_set来表示,这样合并时间复杂度比较低。

 

1. 给每个集合编号为0,1,2,3...

2. 创建一个hash_map,key为字符串,value为一个链表,链表节点为字符串所在集合的编号。

遍历所有的集合,将字符串和对应的集合编号插入到hash_map中去。

 

[空间中的评论有字数限制,所以只能将答案分成多个评论。

3. 创建一个长度等于集合个数的int数组,表示集合间的合并关系。例如,下标为5的元素值为3,表示将下标为5的集合合并到下标为3的集合中去。

开始时将所有值都初始化为-1,表示集合间没有互相合并。

在集合合并的过程中,我们将所有的字符串都合并到编号较小的集合中去。

遍历第二步中生成的hash_map,对于每个value中的链表,首先找到最小的集合编号(有些集合已经被合并过,需要顺着合并关系数组找到合并后的集合编号),然后将链表中所有编号的集合都合并到编号最小的集合中(通过更改合并关系数组)。

4.现在合并关系数组中值为-1的集合即为最终的集合,它的元素来源于所有直接或间接指向它的集合。

 

算法的复杂度为O(n),其中n为所有集合中的元素个数。

 

题目中的例子:

0: {aaa bbb ccc}

1: {bbb ddd}

2: {eee fff}

3: {ggg}

4: {ddd hhh}

生成的hash_map,和处理完每个值后的合并关系数组分别为

aaa: 0。[-1, -1, -1, -1, -1]

bbb: 0, 1。[-1, 0, -1, -1, -1]

ccc: 0。[-1, 0, -1, -1, -1]

ddd: 1, 4。[-1, 0, -1, -1, 0]

eee: 2。[-1, 0, -1, -1, 0]

fff: 2。[-1, 0, -1, -1, 0]

ggg: 3。[-1, 0, -1, -1, 0]

hhh: 4。[-1, 0, -1, -1, 0]

所以合并完后有三个集合,第0,1,4个集合合并到了一起,

第2,3个集合没有进行合并。

 

网易面试题:new/delete和malloc/free的区别

new/delete:给定数据类型,new/delete会自动计算内存大小,并进行分配或释放。如果是对类进行操作,new/delete还会自动调用相应的构造函数和析构函数。

malloc/free:没有进行任何数据类型检查,只负责分配和释放给定大小的内存空间。

有些情况下,new/delete和malloc/free都不能满足性能的要求,我们需要自建内存分配来提高效率。比如,如果程序需要动态分配大量很小的对象,我们可以一次分配可以容纳很多小对象的内存,将这些小对象维护在链表中,当程序需要时直接从链表中返回一个。

还有一点,new返回指定类型的指针;而malloc返回void*,必须强制类型转化。

有个比较有意思的地方是:int *p=(void*)malloc(1);可以编译并运行。

百度面试题:有两个文件,各含50M和500个url,找出共同的url

首先使用包含500个url的文件创建一个hash_set。

然后遍历50M的url记录,如果url在hash_set中,则输出此url并从hash_set中删除这个url。

所有输出的url就是两个记录里相同的url。

 

在这些方法之前,可不可以先将两个文件都分割一下,然后再一一对应着地求交集?比如:将域名理解成由多个n-gram组成,然后将50M文件中的每个域名随机地按其中一个“n-gram” hash到一个子文件中;将小文件中的每个域名按所有的“n-gram”hash到多个子文件中去。由此,两个文件分别被分割为多个子文件,然后只对对应的两两子文件做求交操作。

 

 

迅雷面试题:最快的排序法的性能,并列举至少三个

最快的排序算法是O(N*lgN)。 快速排序,归并排序 堆排序

 

微软面试题:删除链表中的重复项

一个没有排序的链表,比如list={a,l,x,b,e,f,f,e,a,g,h,b,m},请去掉重复项,并保留原顺序,以上链表去掉重复项后为newlist={a,l,x,b,e,f,g,h,m},请写出一个高效算法(时间比空间更重要)。

 

建立一个hash_map,key为链表中已经遍历的节点内容,开始时为空。

从头开始遍历链表中的节点:

- 如果节点内容已经在hash_map中存在,则删除此节点,继续向后遍历;

- 如果节点内容不在hash_map中,则保留此节点,将节点内容添加到hash_map中,继续向后遍历。

 

谷歌面试题:找到两个字符串的公共字符,并按照其中一个的排序。写一函数f(a,b),它带有两个字符串参数并返回一串字符,该字符串只包含在两个串中都有的并按照在a中的顺序。写一个版本算法复杂度O(N^2)和一个O(N) 。

 

O(N^2):

对于a中的每个字符,遍历b中的每个字符,如果相同,则拷贝到新字符串中。

 

O(N):

首先使用b中的字符建立一个hash_map,对于a中的每个字符,检测hash_map中是否存在,如果存在则拷贝到新字符串中。

 

谷歌面试题:在给定整数序列中,找出最大和的子序列

给定一个整数序列,其中有些是负数,有些是正数,从该序列中找出最大和的子序列。比如:-5,20,-4,10,-18,子序列[20,-4,10]具有最大和26。

 

` int GetMaxSubArraySum(int* array, int array_len) {

`    int current_sum = 0;

`    int max_sum = 0;

`    for (int i = 0; i < array_len; ++i) {

`      current_sum += array[i];

`      if (current_sum > max_sum) {

`        max_sum = current_sum;

`      } else if (current_sum < 0) {

`        current_sum = 0;

`      }

`    }

`    return max_sum;

` }

 

谷歌面试题:如何尽快找到一个好人有n个人,其中超过半数是好人,剩下的是坏人。好人只说真话,坏人可能说真话也可能说假话。这n个人互相都知道对方是好人还是坏人。

 

现在要你从这n个人当中找出一个好人来,只能通过以下方式:

每次挑出两个人,让这两个人互相说出对方的身份,你根据两个人的话进行判断。

 

问通过何种方法才能最快的找出一个好人来。(要考虑最坏的情况)

按照这个方法,答案为3n,不知有没有更快的方法。

 

腾讯面试题:用UDP协议通讯时怎样得知目标机是否获得了数据包

以在每个数据包中插入一个唯一的ID,比如timestamp或者递增的int。

发送方在发送数据时将此ID和发送时间记录在本地。

接收方在收到数据后将ID再发给发送方作为回应。

发送方如果收到回应,则知道接收方已经收到相应的数据包;如果在指定时间内没有收到回应,则数据包可能丢失,需要重复上面的过程重新发送一次,直到确定对方收到。

 

网易面试题:没有拷贝构造函数和重载=运算符的string类,c++中,一个没有拷贝构造函数和重载=运算符的string类,会出现什么问题,如何解决?

 

如果没有定义拷贝构造函数和重载=运算符,则系统会自动生成逐位拷贝的函数。

当我们用string初始化string时,(比如 string a("abc"); string b = a;),两个对象会指向同样的内存地址。在两个对象的析构函数中,我们会对同一个内存块调用两次删除,导致不确定的结果。

当我们将一个string赋值给另外一个string时,(比如 string a("abc"); string b(“cde"); b = a;)除了上面的多次调用析构函数的问题外,由于原来对象b指向的数据没有被正确删除,会导致内存泄漏。

 

解决办法:

1. 添加这两个函数。

2. 不使用这两个函数。

- 不用string初始化string:可以使用string a(”abc"); string b(a.c_str()); 代替。

- 不用string给string赋值,包括不能通过传值方法传递string参数:尽量使用指针。

 

百度面试题:芯片测试

有2k块芯片,已知好芯片比坏芯片多。请设计算法从其中找出一片好芯片,说明你所用的比较次数上限。
其中好芯片和其它芯片比较时,能正确给出另一块芯片是好还是坏。坏芯片和其它芯片比较时,会随机的给出好或是坏。

两块芯片比较,结果可能是(好好),(好坏),(坏坏)。如果结果是(好好),则可能是两块好芯片,也可能是两块坏芯片。如果结果是(好坏)或者(坏坏),则两块芯片中至少有一块是坏的。

1. 将2k块芯片分成k组,每组两个比较。如果结果是(好坏)或者(坏坏),则把两块芯片都去掉;如果结果是(好好),则任选其中一块去掉。可以保证剩下好芯片比坏芯片多。

2. 如果剩下1个或2个,则可以判断这些是好芯片。

3. 如果剩下偶数个,则回到第1步。

4. 如果剩下奇数个,则任取一个和其它所有芯片比较,如果有一半将此芯片判为好芯片,则说明此芯片为好芯片,测试结束;否则,说明此芯片为坏芯片,去掉此芯片(当然还可以去掉将此芯片判为好芯片的芯片)后总芯片数为偶数,回到第1步。

 

考虑最坏的情况:

第1次,进行k次比较,可以使芯片数减半;

第2次,k为奇数,则需要进行k-1次比较,使芯片数减为k-1。

第3次,通过(k-1)/ 2次比较可以使芯片数减为 (k-1)/2。

第4次,(k-1)/2为奇数,需要(k-1)/2 - 1次比较

第5次,需要 ((k-1)/2 - 1)/2次比较

..

所以最多4k次比较。

 

请写出下面c语言代码的输出

 

# include<stdio.h>

int main()

{

int a,b,c,d;

a=10;

b=a++;

c=++a;

d=10*a++;

printf("b, c, d: %d, %d, %d", b, c, d);

return 0;

}

a++ 是先用a进行运算,然后在将a的值加1;++a是先将a的值加1,然后再参与运算。

所以答案是 b, c, d: 10, 12, 120

 

迅雷面试题:合并用户基本信息和看电影的记录

如何有效合并两个文件:一个是1亿条的用户基本信息,另一个是用户每天看电影连续剧等的记录,5000万条。其中内存只有1G。

显然内存不能同时存下所有的数据,所以考虑分而治之的思想。

假设1K Byte可以保存一个用户的基本信息和看电影记录。我们可以将基本信息和看电影记录都按照hash(user_name)%100的余数各分成100个小文件。利用1G内存,我们可以每次只处理一对小文件,然后将结果输出到一个文件中即可。

在处理一对小文件时,可以利用key为用户名的hash_map将基本信息和看电影记录合并在一起

 

百度面试题:用C语言将输入的字符串在原串上倒序

` void revert(char* str) {

`    char c;

`    for (int front = 0, int back = strlen(str) - 1;

`         front < back;

`         ++front, --back) {

`      c = str[back];

`      str[back] = str[front];

`      str[front] = c;

`    }

` }

已知一个字串由GBK汉字和ascii编码的数字和字母混合组成,编写c语言函数实现从中去掉所有ascii编码的字母和数字(包括大小写),要求在原字符串上返回结果。

例如: “http://hi.baidu.com/mianshiti 是讨论IT面试题的博客” 会变成 “://../ 是讨论面试题的博客”。

注:函数接口为:int filter_ascii(char* gbk);汉字的GBK编码范围是0x8140-0xFEFE。

 

`int filter_ascii(char* gbk) {

` int new_p = 0;

` int old_p= 0;

` while (gbk[old_p]) {

` if (gbk[old_p] > 0x81 ||

`    gbk[old_p] == 0x81&& gbk[old_p + 1] >= 0x40) {

`   gbk[new_p++] = gbk[old_p++];

`   gbk[new_p++] = gbk[old_p++];

` } else if (gbk[old_p] >= 'a' && gbk[old_p] <= 'z' ||

`    gbk[old_p] >= 'A' && gbk[old_p] <= 'Z' ||

`    gbk[old_p] >= '0' && gbk[old_p] <= '9') {

`   old_p++;

` } else {

`   gbk[new_p++] = gbk[old_p++];

` }

` }

` gbk[new_p] = '\0';

` return 0;

`}

 

一个警察,一个小偷,一个爸爸,一个妈妈,两个儿子,两个女儿,共八个人要过一条河,河上没有桥,只有一条船。
有几个条件必须满足:
1、船一次最多只能坐两个人
2、小偷必须和警察在一起,否则小偷会偷东西
3、爸爸必须和儿子在一起否则妈妈会打儿子
4、妈妈必须和女儿在一起否则爸爸会打女儿
5、只有警察,爸爸,妈妈会划船
现在要他们八个都安全过河,请提出方案。

1. 警察和小偷过去,警察回来

2. 警察和儿子过去,警察和小偷回来

3. 爸爸和儿子过去,爸爸回来

4. 爸爸和妈妈过去,妈妈回来

5. 警察和小偷过去,爸爸回来

6. 爸爸妈妈过去,妈妈回来

7. 妈妈和女儿过去,警察和小偷回来

8. 警察和女儿过去,警察回来

9. 警察和小偷过去

在每一步都看看有几种可能的方案,然后选一种方案进行下去。

如果到哪个地方进行不下去了,则向上回溯,找到另外一种可能方案进行下去....

直到找到一种可行的方案。

 

简直就是一种人肉回溯法。

不知有没有其它方法。

 

编写一个C语言函数,要求输入一个url,输出该url是首页、目录页或者其他url
如下形式叫做首页:
militia.info/
www.apcnc.com.cn/
http://www.cyjzs.comwww.greena888.com/
如下形式叫做目录页:
http://hi.baidu.com/mianshiti/
thursdaythree.net/greenhouses--gas-global-green-house-warming/

请注意:
a) url有可能带http头也有可能不带
b)动态url(即含有"?"的url)的一律不算目录页,如:
www.buddhismcity.net/utility/mailit.php?l=/activity/details/3135/
www.buddhismcity.net/utility/mailit.php?l=/activity/details/2449/ 

` // 0: home, 1: directory, 2: other

` int GetUrlType(char* url) {

` int start_without_http = 0;

` // skip the http head.

` char http_head[] = "http://"

` int i;

` for (i = 0; http_head[i] && url[i]; ++i) {

`   if (url[i] != http_head[i]) {

`    break;

`   }

` }

` if (i == 7) {

`   start_without_http = 7;

` }

` int slash_num = 0;

` for (i = start_without_head; url[i]; ++i) {

`   if (url[i] == '?') {

`    return 2;

`   }

`   if (url[i] == '/') {

`    slash_num++;

`   }  

` }

` if (slash_num > 0 && url[i-1] != '/') {

`   return 2;

` }

` if (slash_num > 1) {

`   return 1;

` }

` return 0;

` }

已知一个无向无环连通图T的所有顶点和边的信息,现需要将其转换为一棵树,要求树的深度最小,请设计一个算法找到所有满足要求的树的根结点,并分析时空复杂度。

最简单直接的方法就是把每个节点都试一遍:

假设某个节点为根节点,计算树的深度。当遍历完所有节点后,也就找到了使树的深度最小的根节点。

但这个方法的复杂度很高。如果有n个节点,则时间复杂度为O(n^2)。

树的深度取决于根节点到最深叶节点的距离,所以我们可以从叶节点入手。

叶节点会且只会和某一个节点连通(反之不成立,因为根节点也可能只和一个节点连通),所以我们很容易找到所有可能的叶节点。

题目可以等价于找到了两个叶节点,使得两个叶节点之间的距离最远。根节点就是这两个叶节点路径的中间点(或者中间两个点的任意一个)。

我们可以每次都将连接度为1的节点删掉,直到最后只剩下1个或2个节点,则这一个节点,或者两个节点中的任意一个,就是我们要找的根节点。

网易面试题:如何把一个正方形按面积5等分?

 

这道题有点脑筋急转弯的感觉。

只要将对边都5等分,然后将对应的点连接起来就可以了。

 

有时还会要求5等分后几个多边形是相互连接的。

可以考虑将正方形的周长5等分,然后连接到中心点就可以了。

应该不是这么简单,不借助工具,没办法五等分,每个顶点连接顺时针的第二条边的中点,得到四个直角形,中间一个正方形,面积都相等。

 

有两个字符串 str1和str2,写一个函数实现在str1中查找str2的初始位置。要求不区分大小写。

从str1的第一个字符到第len(str1)-len(str2)个字符,尝试匹配,如果相等,则返回对应位置。

设str1和str2的长度分别为m和n,这样的复杂度为O(mn)。

使用KMP算法,复杂度为O(m + n)。

KMP算法首先对str2进行预处理。当在某处匹配失败时,不需要回溯str1,而只需要根据预处理的结果回溯str2即可。

至于大小写问题,只需将所有大写字母转成小写字母后再比较即可。

 

序列Seq=[a,b,…z,aa,ab…az,ba,bb,…bz,…,za,zb,…zz,aaa,…] 类似与excel的排列,任意给出一个字符串s=[a-z]+(由a-z字符组成的任意长度字符串),请问s是序列Seq的第几个。

注意到每满26个就会向前进一位,类似一个26进制的问题。

比如ab,则位置为26*1 + 2;

比如za,则位置为26*26 + 1;

比如abc,则位置为26*26*1 + 26*2 + 3;

 

网易面试题:10个人分成4组 有几种分法?

每组的人数可能为下面的值:

1,1,1,7

1,1,2,6

1,1,3,5

1,1,4,4

1,2,2,5

1,2,3,4

1,3,3,3

2,2,2,4

2,2,3,3

下面分别计算每种人数对应的分法数:

1,1,1,7 = 10*9*8/(1*2*3)=120

1,1,2,6 = 10*9/(1*2) * 8*7/(1*2) = 1260

1,1,3,5 = 10*9/(1*2) * 8*7*6/(1*2*3) = 2520

1,1,4,4 = 10*9/(1*2) * 8*7*6*5/(1*2*3*4) / (1*2) = 1575

1,2,2,5 = 10 * 9*8/2 * 7*6/2 /2 = 3780

1,2,3,4 = 10 * 9*8/2 * 7*6*5/(2*3) = 12600

1,3,3,3 = 10 * 9*8*7/(2*3) * 6*5*4/(2*3) / (2*3) = 2800

2,2,2,4 = 10*9/2 * 8*7/2 * 6*5/2 /(2*3) = 3150

2,2,3,3 = 10*9/2 * 8*7/2 * 6*5*4/(2*3) / 2 / 2 = 6300

所以总的分法为

120 + 1260 + 2520 + 1575 + 3780 + 12600 + 2800 + 3150 + 6300 = 34105

 

给出若干个单词,组成字典,要求查找速度最快。

为使查找速度最快,可以要使用hash_map。

如果每个单词还有对应的解释和例句,可以将解释和例句对应的指针存放在hash_map的值中

 

写一段程序,实现atoi(const char* s)方法。

atoi用于将字符串转换成为整数。

比如 “123” =》 123, “-246” =》 -246。

 

`   int atoi(const char*s) {

`     int result = 0;

`     bool is_plus = true;

`     if (*s == '+') {

`       ++s;

`     } else if (*s == '-') {

`       ++s;

`       is_plus = false;

`     }

`     while (*s >= '0' && *s <= '9') {

`       result = result * 10 + *s - '0';

`       ++s;

`     }

`     if (is_plus) {

`       return result;

`     } else {

`       return -result;

`     }

`   }

 

 

写一段程序,找出数组中第k大小的数,输出数所在的位置。例如{2,4,3,4,7}中,第一大的数是7,位置在4。第二大、第三大的数都是4,位置在1、3随便输出哪一个均可。

先找到第k大的数字,然后再遍历一遍数组找到它的位置。所以题目的难点在于如何最高效的找到第k大的数。

 

我们可以通过快速排序,堆排序等高效的排序算法对数组进行排序,然后找到第k大的数字。这样总体复杂度为O(N logN)。

 

我们还可以通过二分的思想,找到第k大的数字,而不必对整个数组排序。

从数组中随机选一个数t,通过让这个数和其它数比较,我们可以将整个数组分成了两部分并且满足,{x, xx, ..., t} < {y, yy, ...}。

在将数组分成两个数组的过程中,我们还可以记录每个子数组的大小。这样我们就可以确定第k大的数字在哪个子数组中。

然后我们继续对包含第k大数字的子数组进行同样的划分,直到找到第k大的数字为止。

平均来说,由于每次划分都会使子数组缩小到原来1/2,所以整个过程的复杂度为O(N)。

 

给定函数d(n) = n + n的各位之和,n为正整数,如 d(78) = 78+7+8=93。 这样这个函数可以看成一个生成器,如93可以看成由78生成。

定义数A:数A找不到一个数B可以由d(B)=A,即A不能由其他数生成。现在要写程序,找出1至10000里的所有符合数A定义的数。

申请一个长度为10000的bool数组,每个元素代表对应的值是否可以有其它数生成。开始时将数组中的值都初始化为false。

由于大于10000的数的生成数必定大于10000,所以我们只需遍历1到10000中的数,计算生成数,并将bool数组中对应的值设置为true,表示这个数可以有其它数生成。

最后bool数组中值为false的位置对应的整数就是不能由其它数生成的。

 

 

13个坏人和13个好人站成一圈,数到7就从圈里面踢出一个来,要求把所有坏人都给踢出来,所有好人都留在圈里。请找出初始时坏人站的位置。

定义一个长度为26的循环链表,节点中存放自己的节点号(顺序从0到25)。

我们从0号节点开始,每向前移动7步就将指向的节点从链表中删掉,直到剩下13个节点。

每次删掉的节点所存储的节点号就是初始时坏人站的位置,将这些位置收集起来就得到了所有坏人的初始位置。

 

求一个论坛的在线人数,假设有一个论坛,其注册ID有两亿个,每个ID从登陆到退出会向一个日志文件中记下登陆时间和退出时间,要求写一个算法统计一天中论坛的用户在线分布,取样粒度为秒

一天总共有 3600*24 = 86400秒。

定义一个长度为86400的整数数组int delta[86400],每个整数对应这一秒的人数变化值,可能为正也可能为负。开始时将数组元素都初始化为0。

然后依次读入每个用户的登录时间和退出时间,将与登录时间对应的整数值加1,将与退出时间对应的整数值减1。

这样处理一遍后数组中存储了每秒中的人数变化情况。

定义另外一个长度为86400的整数数组int online_num[86400],每个整数对应这一秒的论坛在线人数。

假设一天开始时论坛在线人数为0,则第1秒的人数online_num[0] = delta[0]。第n+1秒的人数online_num[n] = online_num[n-1] + delta[n]。

这样我们就获得了一天中任意时间的在线人数。

 

一个小猴子边上有100根香蕉,它要走过50米才能到家,每次它最多搬50根香蕉,每走1米就要吃掉一根,请问它最多能把多少根香蕉搬到家里。

小猴子可以采用如下策略:

小猴子先搬50根,走到1米处,路上吃掉1根,放下48根后返回起始点,并在返回路上吃剩下的1根。然后将起始点处的50根香蕉搬到1米处,又在路上吃掉1根。这样总共消耗了3根香蕉,将所有香蕉向前搬动了1米。采用类似的策略搬动16米后,总共消耗了48根香蕉,还剩下52根香蕉。

如果继续按照同样的策略向前移动到17米处,则剩下49根香蕉;如果直接在16米处丢掉2根香蕉,搬着50根香蕉向前走,在17米处也是有49根香蕉。所以猴子在17米处最多可以保留49根香蕉。

继续搬到家还有33米,所以最后剩的香蕉数16根。

 

在一个文件中有 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。

 

不妨假设10G个整数是64bit的。

 

2G内存可以存放256M个64bit整数。

我们可以将64bit的整数空间平均分成256M个取值范围,用2G的内存对每个取值范围内出现整数个数进行统计。这样遍历一边10G整数后,我们便知道中数在那个范围内出现,以及这个范围内总共出现了多少个整数。

如果中数所在范围出现的整数比较少,我们就可以对这个范围内的整数进行排序,找到中数。如果这个范围内出现的整数比较多,我们还可以采用同样的方法将此范围再次分成多个更小的范围(256M=2^28,所以最多需要3次就可以将此范围缩小到1,也就找到了中数)。

 

 

编一个程序求质数的和,例如F(7) = 2+3+5+7+11+13+17=58。

方法1:

对于从2开始的递增整数n进行如下操作:

用 [2,n-1] 中的数依次去除n,如果余数为0,则说明n不是质数;如果所有余数都不是0,则说明n是质数,对其进行加和。

 

空间复杂度为O(1),时间复杂度为O(n^2),其中n为需要找到的最大质数值(例子对应的值为17)。

方法2:

可以维护一个质数序列,这样当需要判断一个数是否是质数时,只需判断是否能被比自己小的质数整除即可。

 

对于从2开始的递增整数n进行如下操作:

用 [2,n-1] 中的质数(2,3,5,7,开始时此序列为空)依次去除n,如果余数为0,则说明n不是质数;如果所有余数都不是0,则说明n是质数,将此质数加入质数序列,并对其进行加和。

 

空间复杂度为O(m),时间复杂度为O(mn),其中m为质数的个数(例子对应的值为7),n为需要找到的最大质数值(例子对应的值为17)。

方法3:

也可以不用除法,而用加法。

申请一个足够大的空间,每个bit对应一个整数,开始将所有的bit都初始化为0。

对于已知的质数(开始时只有2),将此质数所有的倍数对应的bit都改为1,那么最小的值为0的bit对应的数就是一个质数。对新获得的质数的倍数也进行标注。

对这样获得的质数序列累加就可以获得质数和。

 

空间复杂度为O(n),时间负责度为O(n),其中n为需要找到的最大质数值(例子对应的值为17)。

 

有一个由大小写组成的字符串,现在需要对它进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序)。

初始化两个int变量A和B,代表字符串中的两个位置。开始时A指向字符串的第一个字符,B指向字符串的最后一个字符。

逐渐增加A的值使其指向一个大写字母,逐渐减小B使其指向一个小写字母,交换A,B所指向的字符,然后继续增加A,减小B....。

当A>=B时,就完成了重新排序。

 

小明和小强都是张老师的学生,张老师的生日是M月N日,2人都不知道张老师的生日是下列10组中的一天,张老师把M值告诉了小明,把N值告诉了小强,张老师问他们知道他的生日是那一天吗?

3月4日,3月5日,3月8日

6月4日,6月7日

9月1日,9月5日

12月1日,12月2日,12月8日

小明说:如果我不知道的话,小强肯定也不知道

小强说:本来我也不知道,但是现在我知道了

小明说:哦,那我也知道了

请根据以上对话推断出张老师的生日是哪一天?

根据小明的第一句话:

如果是6月7日的话,则小强开始就知道(因为7日只出现了一次)。小明敢断言小强不知道,那么说明张老师的生日不在6月。

同理,如果是12月2日,则小强开始就知道。小明敢断言小强不知道,说明张老师生日不在12月。

剩下的可能为:

3月4日,3月5日 ,3月8日

9月1日,9月5日

 

根据小强的第一句话:

如果小强拿到的数字是5,则张老师的生日可能是3月5日或者9月5日,小强并不能断定。所以小强拿到的数字不可能是5。

剩下的可能为:

3月4日,3月8日

9月1日

 

根据小明的第二句话:

如果小明拿到的数字是3,则张老师的生日可能是 3月4日或者3月8日 ,小明并不能断定。所以小明难道的数字不可能是3。

 

所以张老师的生日是9月1日。

 

n是一个奇数,求证n(n^2-1)能被24整除。

也就是证明n(n^2-1)能被8和3整除。

 

因为n是奇数,假设n=2x+1,其中x为整数。

n(n^2-1)

=(2x+1)((2x+1)^2-1)

=(2x+1)(4x^2+4x+1-1)

=(2x+1)4x(x+1)

显然x(x+1)能被2整除,所以(2x+1)4x(x+1)能被8整除。

 

x除以3的余数可能是0,1,2,对应x,x+1,2x+1能被3整除。也就是说(2x+1)4x(x+1)能被3整除。

所以n(n^2-1)能被24整除。

 

 

两个整数集合A和B,求其交集。

利用map来解决。

1. 读取整数集合A中的整数,并将读到的整数插入到map中。

2. 读取整数集合B中的整数,如果该整数在map中,则将此数加入到交集当中。

 

如何引用一个已经定义过的全局变量?

可以用引用头文件的方式,也可以使用extern关键字。

用引用头文件方式,如果将那个变量写错了,那么在编译期间会报错。

用extern方式,如果将变量名写错了,那么在编译期间不会报错,而在连接期间报错。

 

 

n个空间(其中n<1M),存放a到a+n-1的数,位置随机且数字不重复,a为正且未知。现在第一个空间的数被误设置为-1。已经知道被修改的数不是最小的。请找出被修改的数字是多少。

例如:n=6,a=2,原始的串为5, 3, 7, 6, 2, 4。现在被别人修改为-1, 3, 7, 6, 2, 4。现在希望找到5。

总结一下

先O(N)取得最小值a

方法1:异或非-1的所有数,再异或a到a+n-1,最后值即被置-1者。

方法2:加一个a到a+n-1中间的数,减一个非-1的数,直到没数,最后值即被置-1者。

方法3:bitmap

 

实现一个函数,对一个正整数n,算得到1需要的最少操作次数。

操作规则为:如果n为偶数,将其除以2;如果n为奇数,可以加1或减1;一直处理下去。

例子:

func(7) = 4,可以证明最少需要4次运算

n = 7

n-1 6

n/2 3

n-1 2

n/2 1

要求:实现函数(实现尽可能高效) int func(unsign int n);n为输入,返回最小的运算次数。

给出思路(文字描述),完成代码,并分析你算法的时间复杂度。

int func(unsign int n) {

if (n == 1) {

return 0;

}

if (n%2 == 0) {

return 1 + func(n/2);

}

int x = func(n+1);

int y = func(n-1);

if (x > y) {

return y + 1;

} else {

return x + 1;

}

}

假设n表示成二进制有x bit,可以看出计算复杂度为O(2^x),也就是O(n)。

将n转换到二进制空间来看(比如7为111,6为110):

- 如果最后一位是0,则对应于偶数,直接进行除2操作。

- 如果最后一位是1,情况则有些复杂。

**如果最后几位是???01,则有可能为???001,???1111101。在第一种情况下,显然应该-1;在第二种情况下-1和+1最终需要的步数相同。所以在???01的情况下,应该选择-1操作。

**如果最后几位是???011,则有可能为???0011,???11111011。在第一种情况下,+1和-1最终需要的步数相同;在第二种情况下+1步数更少些。所以在???011的情况下,应该选择+1操作。

**如果最后有更多的连续1,也应该选择+1操作。

 

如果最后剩下的各位都是1,则有11时应该选择-1;111时+1和-1相同;1111时应选择+1;大于四个1时也应该选择+1;

` int func(unsign int n) {

`    if (n == 1) {

`      return 0;

`    }

`    if (n % 2 == 0) {

`      return 1 + func(n/2);

`    }

`    if (n == 3) {

`      return 2;

`    }

`    if ( n & 2) {

`      return 1 + func(n + 1);

`    } else {

`      return 1 + func(n - 1);

`    }

` }

对任意输入的正整数N,求N!的尾部连续0的个数,并指出计算复杂度。如:18!=6402373705728000,尾部连续0的个数是3。(不用考虑数值超出计算机整数界限的问题)

容易理解,题目等价于求因子2和因子5出现的次数。

对于因子2来说,数字2,4,6,8,10....2n...中存在因子2,这样就获得了 N/2 (其中N/2只取结果的整数部分)个因子2。这些数字去除因子2后,变成1,2,3....N/2,又可以提取N/4个因子2....这样一直到只剩下1个数(1)。所以N!中总共可以获得N/2 + N/4 + N/8 +....个因子2。

同理,N!中可以获得N/5 + N/25 + ... 个因子5。

尾部连续0的个数就是因子2和因子5较少的那个。

 

对于题目中的例子,18!中包含9+4+2+1个因子2,包含3个因子5。所以尾部有3个连续0。

 

计算的复杂度为O(logN)。

 

 

请实现两棵树是否相等的比较,相等返回1,否则返回其他值,并说明算法复杂度。

 

数据结构为:

typedef struct_TreeNode{

char c;

TreeNode *leftchild;

TreeNode *rightchild;

}TreeNode;

函数接口为:int CompTree(TreeNode* tree1,TreeNode* tree2);

注:A、B两棵树相等当且仅当Root->c==RootB-->c,而且A和B的左右子树相等或者左右互换相等。

递归方法(代码未测试,可能存在bug):

int CompTree(TreeNode* tree1,TreeNode* tree2) {

if (tree1 == NULL && tree2 == NULL) {

    return 1;

}

if (tree1 == NULL || tree2 == NULL) {

    return 0;

}

if (tree1->c != tree2.c) {

    retrun 0;

   }

if (CompTree(tree1->leftchild, tree2->leftchil) &&

CompTree(tree1->rightchild, tree2->rightchild) ||

      CompTree(tree1->leftchild, tree2->rightchild) &&

CompTree(tree1->rightchild, tree2->leftchild)) {

    return 1;

}

}