网易、百度等公司面试题整理

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

n=2*k+1;那么n(n^2-1)=4*k(k+1)*(2k+1)=4*6*(1^2+...+k^2),显然能被24整除。

2、do...while和while...do有什么区别(华为)

前者先执行一遍循环体,在进行条件判断;后者先进性判断,然后根据判断结果来决定是否执行;所以前者的循环体至少执行一次。

3、两个整数集合A和B,求其交集(腾讯)

构造一个map,遍历A中的元素,插入到map中;然后遍历B中的元素,看是否在map中出现。

4、如何引用一个已经定义过的全局变量(华为)

可以引用头文件的方式,也可以使用extern关键字;两者的区别在于,如果变量出错了,那么前者将会在编译期间报错,而后者则会等到连接期间报错

5、甲乙丙丁4个人轮流顺序抽签(共4张签)。任何第一个抽中“请客”的人即请大家吃饭。假设:
A) 4张签中共有1张请客的签;
B) 4张签中共有2张请客的签;
C) 4张签中共有3张请客的签;
请问A)、B)、C)三种情况下甲乙丙丁每个人请大家吃饭的概率分别有多大?(迅雷)

A) 第一个人请客的概率 1/4;第二个人请客的概率是 3/4 * 1/3 = 1/4;第三个人请客的概率是 3/4 * 2/3 * 1/2 = 1/4;第四个人请客的概率是 3/4 * 2/3 * 1/2 * 1 = 1/4。
B) 第一个人请客的概率是 2/4 = 1/2;第二个人请客的概率是 2/4 * 2/3 = 1/3;第三个人请客的概率是 2/4 * 1/3 = 1/6;第四个人请客的概率是 0。
C) 第一个人请客的概率是 3/4;第二个人请客的概率是 1/4;第三四个人请客的概率是0。(跟自己想的出入挺大,自己少考虑了比较多的情况)


6、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。(百度)

这个题如果能弄懂题目意图的话,貌似没什么难得;答案奉上:

由于修改的数不是最小的,所以遍历第二个空间到最后一个空间可以得到a的值。
a 到 a+n-1这 n个数的和是 total = na + (n - 1)n/2。
将第二个至最后一个空间的数累加获得 sub_total。
那么被修改的数就是 total - sub_total。(或许应该考虑相加溢出的情况,故应该采用边加边减的方式,或者整体减去a的值,在进行后续计算)

7、兄弟该如何分钱:

妈妈有2000元,要分给她的2个孩子。由哥哥先提出分钱的方式,如果弟弟同意,那么就这么分。但如果弟弟不同意,妈妈会没收1000元,由弟弟提出剩下 1000元的分钱方式,这时如果哥哥同意了,就分掉这剩下的1000元。但如果哥哥也不同意,妈妈会把剩下的1000元也拿走,然后分别只给他们每人100元。问:如果你是哥哥,你会提出什么样的分钱方式,使你有可能得到最多的钱?(最小单位1元)(IBM)

只要模仿海盗分金问题,采用从后往前推的思路就可以了,比较简单。

哥哥提出分配方案时,弟弟是否同意取决于拒绝后是否可以获得更多利益。弟弟分配时,哥哥是否同意也取决于拒绝后是否可以获得更多好处。所以采取由后向前推导的方法。如果在两次分配中弟弟和哥哥都不同意,则弟弟和哥哥各获得100元。弟弟分钱时,为保证哥哥同意,会提出哥哥101元,弟弟899元的分配方法。因为哥哥获得了比拒绝后的更多利益,所以必然会同意。哥哥分钱时,为保证弟弟同意,会提出哥哥1100元,弟弟900元的分配方法。因为弟弟获得了比拒绝后的更多利益,所以必然会同意。也就是说,最终哥哥会提出哥哥1100元,弟弟900元的分配方法。

8、在一个重男轻女的国家里,每个家庭都想生男孩,如果他们生的孩子是女孩,就再生一个,直到生下的是男孩为止。这样的国家,男女比例会是多少?(Google)

我的答案是1:1,或者说接近于1:1,;可以考虑采用数学的方法,对与一个家庭,如果说有N个小孩,则会出现如下情况:

1/2 N=1:1个男孩

1/4 N=2:1个男孩,1个女孩

1/2^n N=n:1个男孩,(n-1)个女孩

我们统计Boy(n)近似为1,Girl(n)=1-(n-1)/2^n,数学上来说当n比较大时,接近于1:1;但是现实生活中n都不是太大,并且人为流产(人工干预)等因素,造成生男生女的概率不相等,所以也就出现了性别失调。

9、随机洗牌问题;给出一种模拟方法,使得洗牌的结果比较随机;(微软)

1. for i:=1 to n do swap(a[i], a[random(1,n)]); // 凑合,但不是真正随机
2. for i:=1 to n do swap(a[i], a[random(i,n)]); // 真正的随机算法
其中,random(a,b)函数用于返回一个从a到b(包括a和b)的随机整数。
2)的时间复杂度O(n), 空间复杂度O(1);

参见:
http://hi.baidu.com/wulei407/blog/item/b6ea451b6572f9fdaf513315.html

http://hi.baidu.com/zhaolijun08/blog/item/9b4cfa944472521dd31b7043.html

10、在100w个数中找最大的前100个数(百度)

应该使用某种数据结构保存迄今最大的100个数。每读到一个新数时,将新数和保存的100个数中的最小一个相比较,如果新数更大些,则替换。这样扫描一遍100w个数也就获得了最大的100个数。
对于保存的100个数的数据结构,应该在最小复杂度的条件下满足
1)可以获得最小的数;
2)将最小数替换为另一个数后可以重新调整,使其可以满足条件1。
可见小根堆可以满足这些条件。
所以应该采用小根堆+扫描的方法。

11、正向最大匹配分词,怎么做最快?(百度)

Trie树(有待进一步研究)

12、session和cache的区别是什么?(百度)

session是针对单个连接(会话)来使用的,主要存储和连接相关的上下文信息,比如登录信息等等。
cache是应用程序级的,主要用来缓存计算结果,减轻服务器负担,并加快响应速度。

13、session和cookie的区别?

由于http是无状态的协议,所以我们需要使用cookie和session来维护服务器和客户端交互过程中的上下文信息。
cookie是存储在客户端的数据。服务器通过在http响应头,或者通过网页中的脚本在客户端中生成cookie。当客户端访问某个页面时,会把符合条件的cookie一并传送给服务器。这样服务器就可以获得以前记录的状态了。
session是存储在服务器端以sessionid作为key数据。服务器通过cookie(或者在url加入sessionid信息)将sessionid传给客户端,当客户端再次请求时,服务端就可以识别这个sessionid,并获得相应的上下文信息了

cookie 和session 的区别:
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE
4、单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K

14、疯子坐飞机问题

飞机上有100个座位,按顺序从1到100编号。有100个乘客,他们分别拿到了从1号到100号的座位,他们按号码顺序登机并应当对号入座,如果 他们发现对应号座位被别人坐了,他会在剩下空的座位随便挑一个坐。现在假如1号乘客疯了 -_-! (其他人没疯),他会在100个座位中随机坐一个座位。那么第100人正确坐自己座位的概率是多少? 注意登机是从1到100按顺序的。

假设飞机上只有两个座位,那么显然第二个人有50%的概率坐在自己的座位上。
假设飞机上只有三个座位,如果1号坐了自己的座位,则3号会坐在自己的座位上;如果1号坐了3号的座位,则3号不能坐到自己的座位上;如果1号坐了2号的座位,则对于3号来说,效果和2号应该坐到1号座位上,但他会随机坐相同,也就是等价于1号没疯,2号疯了,根据上面只有两个座位时的情况,知道这种情况下3号有50%的概率坐到自己的座位上。所以总体来说3号有50%的概率坐到自己的座位上。
.....
以此类推,可以知道当有100个座位时,100号坐到自己座位上的概率为50%

15、谁能先说出总和为100的数(迅雷)

甲乙两人,玩一个游戏。每人轮流说出1-10的一个数字,从甲开始。轮到某个人,使得所有说出的数字的总和等于100,就算谁赢。
请问甲或乙谁有必胜的把握,为什么?

先假设X必胜,Y必输。
那么不论Y最后说出的数字是1还是10,X都能说出一个数使得总数为100。可以推出Y最后一次说之前的总和为89。
同理可以推出Y倒数第二次说之前总和为78。
依次类推,可以知道Y说之前的数字分别为1,12,23 ... 78, 89。
可以看出X就是甲。他的策略就是,先说出1,然后乙说出数字n,甲就说11-n

16、将query按照出现的频度排序(10个1G大小的文件)(百度)
有10个文件,每个文件1G,每个文件的每一行都存放的是用户的query,每个文件的query都可能重复。如何按照query的频度排序?

1)读取10个文件,按照hash(query)%10的结果将query写到对应的文件中。
这样我们就有了10个大小约为1G的文件。任意一个query只会出现在某个文件中。
2)对于1)中获得的10个文件,分别进行如下操作
- 利用hash_map(query,query_count)来统计每个query出现的次数。
- 利用堆排序算法对query按照出现次数进行排序。
- 将排序好的query输出的文件中。
这样我们就获得了10个文件,每个文件中都是按频率排序好的query。
3)对2)中获得的10个文件进行归并排序,并将最终结果输出到文件中。
注:如果内存比较小,在第1)步中可以增加文件数。

17、有1到10w这10w个数,去除2个并打乱次序,如何找出那两个数?(腾讯)

申请10w个bit的空间,每个bit代表一个数字是否出现过。
开始时将这10w个bit都初始化为0,表示所有数字都没有出现过。
然后依次读入已经打乱循序的数字,并将对应的bit设为1。
当处理完所有数字后,根据为0的bit得出没有出现的数字。

18、在1亿条用户记录里,如何快速查询统计出看了5个电影以上的用户?(迅雷)

构建一个hash map,key为用户名,value为已经看过的电影数量。
遍历所有用户记录,然后根据用户名和已经看过电影数量的情况进行处理:
- 如果用户名不在hash map中,则添加对应用户名,并将值设为1。
- 如果用户名对应的值小于5,则将值加1。如果加1后值为5,则输出此用户名。
- 如果用户名对应的值等于5,则不进行任何操作。

19、如果在高速公路上30分钟内看到一辆车开过的几率是0.95,那么在10分钟内看到一辆车开过的几率是多少?(假设为常概率条件下)(Google)

假设10分钟内看到一辆车开过的概率是x,那么没有看到车开过的概率就是1-x,30分钟没有看到车开过的概率是(1-x)^3,也就是0.05。所以得到方程
(1-x)^3 = 0.05 解方程得到x大约是0.63

20、有25匹马,每次比赛只能有5匹马参加,问最少进行几次比赛才可以得到25匹马中跑得最快的前3名?(Google)

最少需要7次。
首先将马分成a,b,c,d,e 5个组,每组5匹,每组单独比赛。然后将每组的第一名放在一起比赛。假设结果如下
a0,a1,a2,a3,a4
b0,b1,b2,b3,b4
c0,c1,c2,c3,c4
d0,d1,d2,d3,d4
e0,e1,e2,e3,e4
其中a, b,c,d,e小组都是按照名次排列(速度a0>a1>a2>a3>a4, b0>b1....)。并第6次比赛的结果为a0>b0>c0>d0>e0。
那么第6次比赛结束后,我们知道最快的一匹为a0。
我们知道第2名的马一定是a1或者b0,所以在接下来的比赛中要包含这两匹马。如果a1快,那么第3名是a2或者b0,如果b0快,那么第3名是a1,b1或者c0。也就是说第2名和第3名一定在a1,a2,b0,b1和c0当中,所以在第7场比赛中包括这5匹马就可以得到第2名和第3名。
所以7次比赛就可以获得前3名的马。

21、根据上排给出十个数,在其下排填出对应的十个数, 要求下排每个数都是上排对应位置的数在下排出现的次数。上排的数:0,1,2,3,4,5,6,7,8,9。(腾讯)

0,1,2,3,4,5,6,7,8,9
6,2,1,0,0,0,1,0,0,0

22、ibm面试题:只有三只酒杯,如何将酒平均分给4个人喝?(IBM)

总共16两酒,4个人喝,平均每人喝4两。
假设下面的三个数是8两,8两和4两酒杯中的酒。
8 8 0
8 5 3
第一个人先喝3两,变成
8 5 0
8 2 3
第二个人先喝2两,变成
8 0 3
8 3 0
5 3 3
5 6 0
2 6 3
2 8 1
第一个人再喝1两,就刚刚喝了4两,变成
2 8 0
0 8 2
0 7 3
3 7 0
3 4 3
6 4 0
6 1 3
第三个人先喝1两,变成
6 0 3
8 0 1
第四个人先喝1两,变成
8 0 0
5 0 3
第三个人再喝3两,就刚刚喝了4两,变成
5 0 0
2 0 3
第二个人再喝2两,就刚刚喝了4两,变成
0 0 3
第四个人再喝3两,就刚刚喝了4两

23、16个硬币,A和B轮流拿走一些,每次拿走的个数只能是1,2,4中的一个数。谁最后拿硬币谁输。
问:A或B有无策略保证自己赢?

B可以保证自己赢。
如果A拿1个,则B拿2个;如果A拿2个,则B拿1个;如果A拿4个,则B拿2个。这样每次AB加起来都是3或者6,所以最后会剩下1个或4个。如果是1个则A直接输了;如果剩下4个,A全拿则输了,如果不全拿,B继续采取上面的策略,最后还是剩下1个,还是A输。

24、网易面试题:警长,逃犯和黑白帽的问题

有一位警长,抓了三个逃犯。现警长决定给他们一次机会。他拿出3顶黑帽子,两顶白帽子,然后往这三个逃犯头上每人戴了一顶帽子,每个逃犯只能看到另外两个逃犯帽子的颜色,不能看到自己帽子的颜色,而且不能进行通讯,不能进行讨论,只能靠自己的推理推出来,如果猜出来了,放一条生路,否则处死。
警长先问第一逃犯,结果第一逃犯猜错了,被杀掉了。
警长问第二个逃犯,结果还是猜错了,同样被杀掉了。
警长再问第三个逃犯,结果第三个逃犯猜对了。
说明一下,每个逃犯在回答问题时,其他逃犯是听不到的。
为什么第三个一定能猜中,请你给出解释。

如果第二个和第三个人都是白帽子,则第一个人肯定可以知道自己是黑帽子。第一个人没有猜对,说明第二和第三个人中肯定有黑帽子。
如果第三个人是白帽子,由于第二和第三个中肯定有黑帽子,那么第二个人肯定是黑帽子。第二个人没有猜对,说明第三个人不是白帽子。
所以第三个人知道自己肯定是黑帽子。

25、海量日志数据,提取出某日访问百度次数最多的那个IP。

IP地址最多有2^32=4G种取值可能,所以不能完全加载到内存中。
可以考虑分而治之的策略,按照IP地址的hash(IP)%1024值,将海量日志存储到1024个小文件中。每个小文件最多包含4M个IP地址。
对于每个小文件,可以构建一个IP作为key,出现次数作为value的hash_map,并记录当前出现次数最多的1个IP地址。
有了1024个小文件中的出现次数最多的IP,我们就可以轻松得到总体上出现次数最多的IP。

26、给40亿个不重复的unsigned int的整数,没排过序的,然后再给几个数,如何快速判断这几个数是否在那40亿个数当中?

unsigned int 的取值范围是0到2^32-1。我们可以申请连续的2^32/8=512M的内存,用每一个bit对应一个unsigned int数字。首先将512M内存都初始化为0,然后每处理一个数字就将其对应的bit设置为1。当需要查询时,直接找到对应bit,看其值是0还是1即可。

27、排序数组中,找出给定数字的出现次数;(微软)

使用二分查找的方法分别找出给定数字的开始和结束位置,最坏情况下时间复杂度为O(logn)。
方法比较直接,不过代码写起来还有些难度。有兴趣的xdjm可以练习一下 :-)

28、给定字符串,删除开始和结尾处的空格,并将中间的多个连续的空格合并成一个。
比如 “ I like http://hi.baidu.com/mianshiti ” 会变成 "I like http://hi.baidu.com/mianshiti"。

void RemoveExtraSpace(char* str) {
bool keep_space = false;
int new_str_end = 0;

for (int i = 0; str[i]; ++i) {
if (str[i] != " ") {
str[new_str_end++] = str[i];
keep_space = true;
} else if (keep_space) {
str[new_str_end++] = str[i];
keep_space = false;
}
}

if (new_str_end > 0 && str[new_str_end - 1] == " ") {
str[new_str_end - 1] = '\0';
} else {
str[new_str_end] = '\0';
}
}
29、微软面试题:正则表达式提取链接地址
写出正则表达式,从一个字符串中提取链接地址。比如下面字符串中
"IT面试题博客中包含很多 <a href=http://hi.baidu.com/mianshiti/blog/category/微软面试题> 微软面试题 </a> "
则需要提取的地址为 " http://hi.baidu.com/mianshiti/blog/category/微软面试题 "
在python中:

import re
p = re.compile('<a(?: [^>]*)+href=([^ >]*)(?: [^>]*)*>')
content = "IT面试题博客中包含很多 <a href=http://hi.baidu.com/mianshiti/blog/category/微软面试题> 微软面试题 </a> "
p.search(content).groups()

这段代码对于给出的例子是足够了,但实际情况中还需要考虑链接地址两边的单引号或者双引号,href的大小写,情况会稍微复杂些。

另外,如果面试者对正则表达式完全没有概念,可以和面试官申请换一道题,一般不会有太大影响。

参考资料:
http://wiki.ubuntu.org.cn/Python正则表达式操作指南

30、从输入URL到显示网页,后台发生了什么?

作为一个软件开发者,你一定会对网络应用如何工作有一个完整的层次化的认知,同样这里也包括这些应用所用到的技术:像浏览器,HTTP,HTML,网络服务器,需求处理等等。

本文将更深入的研究当你输入一个网址的时候,后台到底发生了一件件什么样的事~

1. 首先嘛,你得在浏览器里输入要网址:

image

2. 浏览器查找域名的IP地址

image

导航的第一步是通过访问的域名找出其IP地址。DNS查找过程如下:

  • 浏览器缓存 – 浏览器会缓存DNS记录一段时间。 有趣的是,操作系统没有告诉浏览器储存DNS记录的时间,这样不同浏览器会储存个自固定的一个时间(2分钟到30分钟不等)。
  • 系统缓存 – 如果在浏览器缓存里没有找到需要的记录,浏览器会做一个系统调用(windows里是gethostbyname)。这样便可获得系统缓存中的记录。
  • 路由器缓存 – 接着,前面的查询请求发向路由器,它一般会有自己的DNS缓存。
  • ISP DNS 缓存 – 接下来要check的就是ISP缓存DNS的服务器。在这一般都能找到相应的缓存记录。
  • 递归搜索 – 你的ISP的DNS服务器从跟域名服务器开始进行递归搜索,从.com顶级域名服务器到Facebook的域名服务器。一般DNS服务器的缓存中会有.com域名服务器中的域名,所以到顶级服务器的匹配过程不是那么必要了。

DNS递归查找如下图所示:

500px-An_example_of_theoretical_DNS_recursion_svg

DNS有一点令人担忧,这就是像wikipedia.org 或者 facebook.com这样的整个域名看上去只是对应一个单独的IP地址。还好,有几种方法可以消除这个瓶颈:

  • 循环 DNS 是DNS查找时返回多个IP时的解决方案。举例来说,Facebook.com实际上就对应了四个IP地址。
  • 负载平衡器 是以一个特定IP地址进行侦听并将网络请求转发到集群服务器上的硬件设备。 一些大型的站点一般都会使用这种昂贵的高性能负载平衡器。
  • 地理 DNS 根据用户所处的地理位置,通过把域名映射到多个不同的IP地址提高可扩展性。这样不同的服务器不能够更新同步状态,但映射静态内容的话非常好。
  • Anycast 是一个IP地址映射多个物理主机的路由技术。 美中不足,Anycast与TCP协议适应的不是很好,所以很少应用在那些方案中。

大多数DNS服务器使用Anycast来获得高效低延迟的DNS查找。

3. 浏览器给web服务器发送一个HTTP请求

image

因为像Facebook主页这样的动态页面,打开后在浏览器缓存中很快甚至马上就会过期,毫无疑问他们不能从中读取。

所以,浏览器将把一下请求发送到Facebook所在的服务器:

GET http://facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]

GET 这个请求定义了要读取的URL: “http://facebook.com/”。 浏览器自身定义 (User-Agent 头), 和它希望接受什么类型的相应 (Accept and Accept-Encoding 头). Connection头要求服务器为了后边的请求不要关闭TCP连接。

请求中也包含浏览器存储的该域名的cookies。可能你已经知道,在不同页面请求当中,cookies是与跟踪一个网站状态相匹配的键值。这样cookies会存储登录用户名,服务器分配的密码和一些用户设置等。Cookies会以文本文档形式存储在客户机里,每次请求时发送给服务器。

用来看原始HTTP请求及其相应的工具很多。作者比较喜欢使用fiddler,当然也有像FireBug这样其他的工具。这些软件在网站优化时会帮上很大忙。

除了获取请求,还有一种是发送请求,它常在提交表单用到。发送请求通过URL传递其参数(e.g.: http://robozzle.com/puzzle.aspx?id=85)。发送请求在请求正文头之后发送其参数。

像“http://facebook.com/”中的斜杠是至关重要的。这种情况下,浏览器能安全的添加斜杠。而像“http: //example.com/folderOrFile”这样的地址,因为浏览器不清楚folderOrFile到底是文件夹还是文件,所以不能自动添加 斜杠。这时,浏览器就不加斜杠直接访问地址,服务器会响应一个重定向,结果造成一次不必要的握手。 

4. facebook服务的永久重定向响应

image

图中所示为Facebook服务器发回给浏览器的响应:

HTTP/1.1 301 Moved Permanently
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Location: http://www.facebook.com/
P3P: CP="DSP LAW"
Pragma: no-cache
Set-Cookie: made_write_conn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT;
path=/; domain=.facebook.com; httponly
Content-Type: text/html; charset=utf-8
X-Cnection: close
Date: Fri, 12 Feb 2010 05:09:51 GMT
Content-Length: 0

服务器给浏览器响应一个301永久重定向响应,这样浏览器就会访问“http://www.facebook.com/” 而非“http://facebook.com/”。

为什么服务器一定要重定向而不是直接发会用户想看的网页内容呢?这个问题有好多有意思的答案。

其中一个原因跟搜索引擎排名有 关。你看,如果一个页面有两个地址,就像http://www.igoro.com/ 和http://igoro.com/,搜索引擎会认为它们是两个网站,结果造成每一个的搜索链接都减少从而降低排名。而搜索引擎知道301永久重定向是 什么意思,这样就会把访问带www的和不带www的地址归到同一个网站排名下。

还有一个是用不同的地址会造成缓存友好性变差。当一个页面有好几个名字时,它可能会在缓存里出现好几次。

5. 浏览器跟踪重定向地址

image

现在,浏览器知道了“http://www.facebook.com/”才是要访问的正确地址,所以它会发送另一个获取请求:

GET http://www.facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Cookie: lsd=XW[...]; c_user=21[...]; x-referer=[...]
Host: www.facebook.com

头信息以之前请求中的意义相同。

6. 服务器“处理”请求

image

服务器接收到获取请求,然后处理并返回一个响应。

这表面上看起来是一个顺向的任务,但其实这中间发生了很多有意思的东西- 就像作者博客这样简单的网站,何况像facebook那样访问量大的网站呢!

  • Web 服务器软件
    web服务器软件(像IIS和阿帕奇)接收到HTTP请求,然后确定执行什么请求处理来处理它。请求处理就是一个能够读懂请求并且能生成HTML来进行响应的程序(像ASP.NET,PHP,RUBY...)。

    举 个最简单的例子,需求处理可以以映射网站地址结构的文件层次存储。像http://example.com/folder1/page1.aspx这个地 址会映射/httpdocs/folder1/page1.aspx这个文件。web服务器软件可以设置成为地址人工的对应请求处理,这样 page1.aspx的发布地址就可以是http://example.com/folder1/page1。

  • 请求处理
    请求处理阅读请求及它的参数和cookies。它会读取也可能更新一些数据,并讲数据存储在服务器上。然后,需求处理会生成一个HTML响应。

所 有动态网站都面临一个有意思的难点 -如何存储数据。小网站一半都会有一个SQL数据库来存储数据,存储大量数据和/或访问量大的网站不得不找一些办法把数据库分配到多台机器上。解决方案 有:sharding (基于主键值讲数据表分散到多个数据库中),复制,利用弱语义一致性的简化数据库。

委 托工作给批处理是一个廉价保持数据更新的技术。举例来讲,Fackbook得及时更新新闻feed,但数据支持下的“你可能认识的人”功能只需要每晚更新 (作者猜测是这样的,改功能如何完善不得而知)。批处理作业更新会导致一些不太重要的数据陈旧,但能使数据更新耕作更快更简洁。

7. 服务器发回一个HTML响应

image

图中为服务器生成并返回的响应:

HTTP/1.1 200 OK
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
P3P: CP="DSP LAW"
Pragma: no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
X-Cnection: close
Transfer-Encoding: chunked
Date: Fri, 12 Feb 2010 09:05:55 GMT

2b3Tn@[...]

整个响应大小为35kB,其中大部分在整理后以blob类型传输。

内容编码头告诉浏览器整个响应体用gzip算法进行压缩。解压blob块后,你可以看到如下期望的HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"    
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" id="facebook" class=" no_js">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-language" content="en" />
...

关于压缩,头信息说明了是否缓存这个页面,如果缓存的话如何去做,有什么cookies要去设置(前面这个响应里没有这点)和隐私信息等等。

请注意报头中把Content-type设置为“text/html”。报头让浏览器将该响应内容以HTML形式呈现,而不是以文件形式下载它。浏览器会根据报头信息决定如何解释该响应,不过同时也会考虑像URL扩展内容等其他因素。

8. 浏览器开始显示HTML

在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了:

image

9. 浏览器发送获取嵌入在HTML中的对象

image

在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。

下面是几个我们访问facebook.com时需要重获取的几个URL:

  • 图片
    http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif
    http://static.ak.fbcdn.net/rsrc.php/zBS5C/hash/7hwy7at6.gif
  • CSS 式样表
    http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css
    http://static.ak.fbcdn.net/rsrc.php/zANE1/hash/cvtutcee.css
  • JavaScript 文件
    http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js
    http://static.ak.fbcdn.net/rsrc.php/z6R9L/hash/cq2lgbs8.js

这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等...

但 不像动态页面那样,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取。服务器的响应中包含了静态文件保存的期限 信息,所以浏览器知道要把它们缓存多长时间。还有,每个响应都可能包含像版本号一样工作的ETag头(被请求变量的实体值),如果浏览器观察到文件的版本 ETag信息已经存在,就马上停止这个文件的传输。

试着猜猜看“fbcdn.net”在地址中代表什么?聪明的答案是"Facebook内容分发网络"。Facebook利用内容分发网络(CDN)分发像图片,CSS表和JavaScript文件这些静态文件。所以,这些文件会在全球很多CDN的数据中心中留下备份。

静态内容往往代表站点的带宽大小,也能通过CDN轻松的复制。通常网站会使用第三方的CDN。例如,Facebook的静态文件由最大的CDN提供商Akamai来托管。

举例来讲,当你试着ping static.ak.fbcdn.net的时候,可能会从某个akamai.net服务器上获得响应。有意思的是,当你同样再ping一次的时候,响应的服务器可能就不一样,这说明幕后的负载平衡开始起作用了。

10. 浏览器发送异步(AJAX)请求

image

在Web 2.0伟大精神的指引下,页面显示完成后客户端仍与服务器端保持着联系。

以 Facebook聊天功能为例,它会持续与服务器保持联系来及时更新你那些亮亮灰灰的好友状态。为了更新这些头像亮着的好友状态,在浏览器中执行的 JavaScript代码会给服务器发送异步请求。这个异步请求发送给特定的地址,它是一个按照程式构造的获取或发送请求。还是在Facebook这个例 子中,客户端发送给http://www.facebook.com/ajax/chat/buddy_list.php一个发布请求来获取你好友里哪个 在线的状态信息。

提起这个模式,就必须要讲讲"AJAX"-- “异步JavaScript 和 XML”,虽然服务器为什么用XML格式来进行响应也没有个一清二白的原因。再举个例子吧,对于异步请求,Facebook会返回一些JavaScript的代码片段。

除了其他,fiddler这个工具能够让你看到浏览器发送的异步请求。事实上,你不仅可以被动的做为这些请求的看客,还能主动出击修改和重新发送它们。AJAX请求这么容易被蒙,可着实让那些计分的在线游戏开发者们郁闷的了。(当然,可别那样骗人家~)

Facebook聊天功能提供了关于AJAX一个有意思的问题案例:把数据从服务器端推送到客户端。因为HTTP是一个请求-响应协议,所以聊天服务器不能把新消息发给客户。取而代之的是客户端不得不隔几秒就轮询下服务器端看自己有没有新消息。

这些情况发生时长轮询是个减轻服务器负载挺有趣的技术。如果当被轮询时服务器没有新消息,它就不理这个客户端。而当尚未超时的情况下收到了该客户的新消息,服务器就会找到未完成的请求,把新消息做为响应返回给客户端。

http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/ 

31、给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数。

产生K个数(k>1) 假定产生的数分别为n1,n2,n3,n4...
那么定义产生的数为n1-1+(n2-2)*5+(n3-1)*5^2+(n4-1)*5^3........
于是产生的数位于区间(0,5^k-1)
然后把5^k分成k等分,产生的数位于哪个等分就是那个产生的随机数(0~6),然后+1即可
如果位于k等分的余数范围,则重新执行一次上述过程
不用担心余数问题,当k取3时落到余数范围的概率就已经降低为6/125
32、编程实现两个正整数的除法,当然不能用除法操作符。
// return x/y.
int div(const int x, const int y) {
....
}

// return x/y

int div(const int x, const int y) {
int left_num = x;
int result = 0;
while (left_num >= y) {
int multi = 1;
while (y * multi <= (left_num >> 1)) {
multi = multi << 1;
}
result += multi;
left_num -= y * multi;
}
return result;
}

扩展问题:
如果需要测试上面这个函数,需要哪些测试用例?

33、微软面试题:利用天平砝码,三次将140克的盐 分成50、90克两份?有一个天平,2克和7克砝码各一个。如何利用天平砝码在三次内将140克盐分成50,90克两份。

第一种方法:
第一次:先称 7+2克盐 (相当于有三个法码2,7,9)
第二次:称2+7+9=18克盐 (相当于有2,7,9,18四个法码)
第三次:称7+18=x+2,得出x是23,23+9+18=50克盐.
剩下就是90克了.

第二种方法:
1.先把140克盐分为两份,每份70克
2.在把70克分为两份,每份35克
3.然后把两个砝码放在天平两边,把35克面粉分成两份也放在两边(15+7=20+2)
现在有四堆面粉70,35,15,20,分别组合得到
70+20=90
35+15=50

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

北极点满足这个条件。
距离南极点很近的一个圈上也满足这个条件。在这个圆圈上,向南走一公里,然后向东走一公里恰好绕南极点一圈,向北走一公里回到原点。所以地球上总共有无数点满足这个条件。事实上距离南极弧长满足1+1/(2*k*pi)维度上的点都满足

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

定义长度为1000的数组。
对于数据流中的前1000个关键字,显然都要放到数组中。
对于数据流中的的第n(n>1000)个关键字,我们知道这个关键字被随机选中的概率为 1000/n。所以我们以 1000/n 的概率用这个关键字去替换数组中的随机一个。这样就可以保证所有关键字都以 1000/n的概率被选中。
对于后面的关键字都进行这样的处理,这样我们就可以保证数组中总是保存着1000个随机关键字。

#include <iostream>
#include <time.h>
using namespace std;
#define MAX_SIZE 10
static void resample(int data, int *sample_list, int n)
{
int idx;
if(n<MAX_SIZE)
sample_list[n]=data;
else
{
srand(time(NULL));
idx=rand()%n;
if(idx<MAX_SIZE)
{
sample_list[idx]=data;

}
}
}
int main()
{
int sample_list[MAX_SIZE];
int count=0,num;
cin>>num;
while(num!=0)
{
resample(num, sample_list,count);
count++;
cin>>num;
}
for(int i=0;i<MAX_SIZE;i++)
cout<<sample_list[i]<<endl;
system("pause");
return 0;
}

36、谷歌面试题:判断一个自然数是否是某个数的平方;判断一个自然数是否是某个数的平方。当然不能使用开方运算。

方法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中的乘法,所以速度会更快些。

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

从标注成既有苹果也有橘子的水果篮中选取一个进行检查。
如果是橘子,则此篮中只有橘子;标有橘子的水果篮中只有苹果;标有苹果的水果篮中既有苹果也有橘子。
如果是苹果,则此篮中只有苹果;标有苹果的水果篮中只有橘子;标有橘子的水果篮中既有苹果也有橘子。

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

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

39、微软面试题:不利用浮点运算,画一个圆
不利用浮点运算,在屏幕上画一个圆 (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之间的差值。

40、微软面试题:快速求取一个整数的7倍
给出一种快速求一个整数7倍的方法。

乘法相对比较慢,所以快速的方法就是将这个乘法转换成加减法和移位操作。
可以将此整数先左移三位(×8)然后再减去原值:X << 3 - X。

41、微软面试题:三只蚂蚁不相撞的概率是多少
在三角形的三个顶点上各有一只蚂蚁,它们向另一个顶点运动,目标随机(可能为另外两个顶点的任意一个)。问三只蚂蚁不相撞的概率是多少?
如果蚂蚁顺时针爬行记为0,逆时针爬行记为1。那么三只蚂蚁的状态可能为000,001,...,110,111中的任意一个,且为每种状态的概率相等。在这8种状态中,只有000和111可以避免相撞,所以蚂蚁不相撞的概率是1/4。

题目的一些扩展:
1. 如果将三角形变成正方形,并且有四只蚂蚁,那么不相撞的概率是多少?
2. 如果将正方形的对角线相连,蚂蚁也可以在对角线上爬行,那么不相撞的概率是多少?(假设对角线相交处有立交桥)

方法1.
对数组进行排序(快速,堆),然后比较相邻的元素是否相同。
时间复杂度为O(nlogn),空间复杂度为O(1)。
42、判断数组中是否包含重复数字
给定一个长度为N的数组,其中每个元素的取值范围都是1到N。判断数组中是否有重复的数字。(原数组不必保留)
方法2.
使用bitmap方法。
定义长度为N/8的char数组,每个bit表示对应数字是否出现过。遍历数组,使用 bitmap对数字是否出现进行统计。
时间复杂度为O(n),空间复杂度为O(n)。

方法3.
遍历数组,假设第 i 个位置的数字为 j ,则通过交换将 j 换到下标为 j 的位置上。直到所有数字都出现在自己对应的下标处,或发生了冲突。
时间复杂度为O(n),空间复杂度为O(1)。

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

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

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

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

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

方法1.
将整个整数流保存到一个数组中,然后再随机选取。
如果整数流很长,无法保存下来,则此方法不能使用。

方法2.
如果整数流在第一个数后结束,则我们必定会选第一个数作为随机数。
如果整数流在第二个数后结束,我们选第二个数的概率为1/2。我们以1/2的概率用第2个数替换前面选的随机数,得到满足条件的新随机数。
....
如果整数流在第n个数后结束,我们选第n个数的概率为1/n。我们以1/n的概率用第n个数替换前面选的随机数,得到满足条件的新随机数。
....
利用这种方法,我们只需保存一个随机数,和迄今整数流的长度即可。所以可以处理任意长的整数流。

45、谷歌面试题:设计方便提取中数的数据结构
设计一个数据结构,其中包含两个函数,1.插入一个数字,2.获得中数。并估计时间复杂度。

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

2. 使用排序数组存储。
插入数字时,在O(logn)时间内找到要插入的位置,在O(n)时间里移动元素并将新数字插入到合适的位置。
获得中数时,在O(1)复杂度内找到中数。

3. 使用大根堆和小根堆存储。
使用大根堆存储较小的一半数字,使用小根堆存储较大的一半数字。
插入数字时,在O(logn)时间内将该数字插入到对应的堆当中,并适当移动根节点以保持两个堆数字相等(或相差1)。
获取中数时,在O(1)时间内找到中数。

46、谷歌面试题:在一个特殊数组中进行查找
给定一个固定长度的数组,将递增整数序列写入这个数组。当写到数组尾部时,返回数组开始重新写,并覆盖先前写过的数。
请在这个特殊数组中找出给定的整数。

假设数组为a[0, 1, ..., N-1]。
我们可以采用类似二分查找的策略。
首先比较a[0]和a[N/2],如果a[0] < a[N/2],则说明a[0,1,...,N/2]为递增子序列,否则另一部分是递增子序列。
然后判断要找的整数是否在递增子序列范围内。如果在,则使用普通的二分查找方法继续查找;如果不在,则重复上面的查找过程,直到找到或者失败为止。

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

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

48、雅虎面试题:HTTP中Get和Post的区别
解释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方法更加安全。

49、雅虎面试题:灯炮能亮的概率是多少?
一个开关接一个灯炮,开关一年内坏掉的概率为10%,灯炮一年内坏掉的概率为20%,问一年后打开开关,灯炮能亮的概率是多少?(假定其他设备都不损坏)
开关好的概率是90%,灯泡好的概率是80%,
所以两个都好的概率是90%*80%=72%。

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

IP协议是网络层的协议。IP协议规定每个互联网网上的电脑都有一个唯一的IP地址,这样数据包就可以通过路由器的转发到达指定的电脑。但IP协议并不保证数据传输的可靠性。
TCP协议是传输层的协议。它向下屏蔽了IP协议不能可靠传输的缺点,向上提供面向连接的可靠的数据传输。
UDP协议也是传输层的协议。它提供无连接的不可靠传输。

51、华为面试题:全局变量和局部变量有什么区别?
全局变量和局部变量有什么区别?怎么实现的?操作系统和编译器是怎么知道的?

全局变量是整个程序都可访问的变量,生存期从程序开始到程序结束;局部变量存在于模块中(比如某个函数),只有在模块中才可以访问,生存期从模块开始到模块结束。
全局变量分配在全局数据段,在程序开始运行的时候被加载。局部变量则分配在程序的堆栈中。因此,操作系统和编译器可以通过内存分配的位置来知道来区分全局变量和局部变量。

52、华为面试题: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。

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

memcpy和memmove都是将源地址的若干个字符拷贝到目标地址。
如果源地址和目标地址有重叠,则memcpy不能保证拷贝正确,但memmove可以保证拷贝正确。

例如:
char src[20];
// set src
char* dst = src + 5;
此时如果要从src拷贝10个字符到dst,则么memcpy不能保证拷贝正确,但是memmove可以保证。

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

const用来说明所定义的变量是只读的。
这些在编译期间完成,编译器可能使用常数直接替换掉对此变量的引用。

更多阅读:
http://www.92ask.net/Archive/?action=show&id=18
初探编译器static、const之实现原理

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

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

每个域名的平均长度为25个字节(估计值),每个IP为4个字节,所以Cache的每个条目需要大概30个字节。
总共50M个条目,所以需要1.5G个字节的空间。可以放置在内存中。(考虑到每秒5000次操作的限制,也只能放在内存中。)

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

可以采用以下策略来提高系统性能:
使用双数组字典树,较字典树相比,能够提高系统效率
使用LIRS替换算法而不是LRU算法,LIRS算法能够根据访问时间和访问频率分成hot和cold两个数据结构
使用一次查询两次返回机制,用户发出大量DNS请求时,对存在于cache中的请求首先返回,然后不存在的域名解析再进一步查询DNS服务器,然后再次返回结果,以减少系统响应时间。

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

由于优惠券可以代替现金,所以可以使用200元优惠券买东西,然后还可以获得100元的优惠券。
假设开始时花了x元,那么可以买到 x + x/2 + x/4 + ...的东西。所以实际上折扣是50%.(当然,大部分时候很难一直兑换下去,所以50%是折扣的上限)

如果使用优惠券买东西不能获得新的优惠券,那么
总过花去了200元,可以买到200+100元的商品,所以实际折扣为 200/300 = 67%.

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

如果开始买3瓶,那么可以四个人喝,并且还能剩一个空瓶。
如果开始买9瓶,可以13个人喝,最后还剩一个空瓶。
如果开始买18瓶,那么26个人喝,可以剩下两个空瓶。
如果开始买19瓶,那么27个人喝,最后剩下三个空瓶。所以最少买19瓶。

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

58、给定一个字符串的集合,格式如:{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个集合没有进行合并

58、一个没有排序的链表,比如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中,继续向后遍历。

59、用UDP协议通讯时怎样得知目标机是否获得了数据包?

可以在每个数据包中插入一个唯一的ID,比如timestamp或者递增的int。
发送方在发送数据时将此ID和发送时间记录在本地。
接收方在收到数据后将ID再发给发送方作为回应。
发送方如果收到回应,则知道接收方已经收到相应的数据包;如果在指定时间内没有收到回应,则数据包可能丢失,需要重复上面的过程重新发送一次,直到确定对方收到。

关于UDP协议的简单介绍,可以参考
http://baike.baidu.com/view/30509.htm

60、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参数:尽量使用指针。

61、百度面试题:芯片测试
有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次比较。

62、请写出下面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

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

显然内存不能同时存下所有的数据,所以考虑分而治之的思想。
假设1K Byte可以保存一个用户的基本信息和看电影记录。我们可以将基本信息和看电影记录都按照hash(user_name)%100的余数各分成100个小文件。利用1G内存,我们可以每次只处理一对小文件,然后将结果输出到一个文件中即可。
在处理一对小文件时,可以利用key为用户名的hash_map将基本信息和看电影记录合并在一起。

64、百度面试题:删除所有ascii编码的字符和数字
2010-03-14 08:59
已知一个字串由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;
`}

65、网易面试题: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

66、一普查员问一女人,“你有多少个孩子,他们多少岁?”女人回答:“我有三个孩子,他们的岁数相乘是36,岁数相加就等于旁边屋的门牌号码。“普查员立刻走到旁边屋,看了一看,回来说:“我还需要多少资料。”女人回答:“我现在很忙,我最大的孩子正在楼上睡觉。”普查员说:”谢谢,我己知道了。”
问题:那三个孩子的岁数是多少。

36 = 1 × 2 × 2 × 3 × 3
所有的可能为
1,1,36;sum = 38
1,2,18;sum = 21
1,3,12;sum = 16
1,4,9;sum = 14
1,6,6;sum = 13
2,2,9;sum = 13
2,3,6;sum = 11
3,3,4;sum = 10
由于普查员知道了年龄和之后还是不能确定每个孩子的年龄,所以可能性为
1,6,6;sum = 13
2,2,9;sum = 13
由于最大(暗含只有一个最大)的孩子在睡觉,所以只可能是
2,2,9;sum = 13

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

定义一个长度为26的循环链表,节点中存放自己的节点号(顺序从0到25)。
我们从0号节点开始,每向前移动7步就将指向的节点从链表中删掉,直到剩下13个节点。
每次删掉的节点所存储的节点号就是初始时坏人站的位置,将这些位置收集起来就得到了所有坏人的初始位置。

68、求一个论坛的在线人数,假设有一个论坛,其注册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]。
这样我们就获得了一天中任意时间的在线人数。

69、一个小猴子边上有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根。

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

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

2G内存可以存放256M个64bit整数。
我们可以将64bit的整数空间平均分成256M个取值范围,用2G的内存对每个取值范围内出现整数个数进行统计。这样遍历一边10G整数后,我们便知道中数在那个范围内出现,以及这个范围内总共出现了多少个整数。
如果中数所在范围出现的整数比较少,我们就可以对这个范围内的整数进行排序,找到中数。如果这个范围内出现的整数比较多,我们还可以采用同样的方法将此范围再次分成多个更小的范围(256M=2^28,所以最多需要3次就可以将此范围缩小到1,也就找到了中数)。

71、编一个程序求质数的和,例如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)。

参考:http://hi.baidu.com/mianshiti/blog

阅读更多
换一批

没有更多推荐了,返回首页