腾讯面试题:tcp三次握手的过程,accept发生在三次握手哪一个阶段?c++
答accept发生在三次握手以后。面试
第一次握手:客户端发送syn包(syn=j)到服务器。算法
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时本身也发送一个ASK包(ask=k)。数据库
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)。编程
三次握手完成后,客户端和服务器就创建了tcp链接。这时能够调用accept函数得到此链接。数组
const的含义及实现机制,好比:const int i,是怎么作到i只可读的?浏览器
const用来讲明所定义的变量是只读的。缓存
这些在编译期间完成,编译器可能使用常数直接替换掉对此变量的引用。安全
用UDP协议通信时怎样得知目标机是否得到了数据包服务器
能够在每一个数据包中插入一个惟一的ID,好比timestamp或者递增的int。
发送方在发送数据时将此ID和发送时间记录在本地。
接收方在收到数据后将ID再发给发送方做为回应。
发送方若是收到回应,则知道接收方已经收到相应的数据包;若是在指定时间内没有收到回应,则数据包可能丢失,须要重复上面的过程从新发送一次,直到肯定对方收到。
求一个论坛的在线人数,假设有一个论坛,其注册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]。
这样咱们就得到了一天中任意时间的在线人数。
在一个文件中有 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。
不妨假设10G个整数是64bit的。
2G内存能够存放256M个64bit整数。
咱们能够将64bit的整数空间平均分红256M个取值范围,用2G的内存对每一个取值范围内出现整数个数进行统计。这样遍历一边10G整数后,咱们便知道中数在那个范围内出现,以及这个范围内总共出现了多少个整数。
若是中数所在范围出现的整数比较少,咱们就能够对这个范围内的整数进行排序,找到中数。若是这个范围内出现的整数比较多,咱们还能够采用一样的方法将此范围再次分红多个更小的范围(256M=2^28,因此最多须要3次就能够将此范围缩小到1,也就找到了中数)。
两个整数集合A和B,求其交集。
1. 读取整数集合A中的整数,将读到的整数插入到map中,并将对应的值设为1。
2. 读取整数集合B中的整数,若是该整数在map中而且值为1,则将此数加入到交集当中,并将在map中的对应值改成2。
经过更改map中的值,避免了将一样的值输出两次。
2. 也能够将A和B分别排序,而后利用归并的思想搞定。
有1到10w这10w个数,去除2个并打乱次序,如何找出那两个数?
申请10w个bit的空间,每一个bit表明一个数字是否出现过。
开始时将这10w个bit都初始化为0,表示全部数字都没有出现过。
而后依次读入已经打乱循序的数字,并将对应的bit设为1。
当处理完全部数字后,根据为0的bit得出没有出现的数字。
首先计算1到10w的和,平方和。
而后计算给定数字的和,平方和。
两次的到的数字相减,能够获得这两个数字的和,平方和。
因此咱们有
x + y = n
x^2 + y^2 = m
解方程能够获得x和y的值。
有1000瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水24小时后就会死亡,至少要多少只小白鼠才能在24小时时鉴别出那瓶水有毒?
最容易想到的就是用1000只小白鼠,每只喝一瓶。但显然这不是最好答案。
既然每只小白鼠喝一瓶不是最好答案,那就应该每只小白鼠喝多瓶。那每只应该喝多少瓶呢?
首先让咱们换种问法,若是有x只小白鼠,那么24小时内能够从多少瓶水中找出那瓶有毒的?
因为每只小白鼠都只有死或者活这两种结果,因此x只小白鼠最大能够表示2^x种结果。若是让每种结果都对应到某瓶水有毒,那么也就能够从2^x瓶水中找到有毒的那瓶水。那如何来实现这种对应关系呢?
第一只小白鼠喝第1到2^(x-1)瓶,第二只小白鼠喝第1到第2^(x-2)和第2^(x-1)+1到第2^(x-1) + 2^(x-2)瓶....以此类推。
回到此题,总过1000瓶水,因此须要最少10只小白鼠。
根据上排给出十个数,在其下排填出对应的十个数, 要求下排每一个数都是上排对应位置的数在下排出现的次数。上排的数: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
经过一个循环作,三次循环搞定.
任意0-N,都是N-3的位置是1,前面是N-4,2,1,其余是0
给40亿个不重复的unsigned int的整数,没排过序的,而后再给几个数,如何快速判断这几个数是否在那40亿个数当中?
unsigned int 的取值范围是0到2^32-1。咱们能够申请连续的2^32/8=512M的内存,用每个bit对应一个unsigned int数字。首先将512M内存都初始化为0,而后每处理一个数字就将其对应的bit设置为1。当须要查询时,直接找到对应bit,看其值是0仍是1便可。
IBM面试题:c++中引用和指针有什么不一样?指针加上什么限制等于引用?
引用不是一个变量,它只表示该引用名是目标变量名的一个别名,它自己不是一种数据类型,所以引用自己不占存储单元,系统也不给引用分配存储单元。引用一经肯定就不能修改。
指针是一个变量,须要在内存中分配空间,此空间中存储所指对象的地址。因为指针是一个普通变量,因此其值还能够经过从新赋值来改变。
把指针定义为const后,其值就不能改变了,功能和引用相似,但有本质的区别。
谷歌面试题:1024! 末尾有多少个0?
末尾0的个数取决于乘法中因子2和5的个数。显然乘法中因子2的个数大于5的个数,因此咱们只需统计因子5的个数。
是5的倍数的数有: 1024 / 5 = 204个
是25的倍数的数有:1024 / 25 = 40个
是125的倍数的数有:1024 / 125 = 8个
是625的倍数的数有:1024 / 625 = 1个
因此1024! 中总共有204+40+8+1=253个因子5。
也就是说1024! 末尾有253个0。
谷歌面试题:给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数
只要咱们能够从 n 个数中随机选出 1 到 n 个数,反复进行这种运算,直到剩下最后一个数便可。
咱们能够调用 n 次给定函数,生成 n 个 1 到 5 之间的随机数,选取最大数所在位置便可知足以上要求。
例如
初始的 7 个数 [1,2,3,4,5,6,7].
7 个 1 到 5 的随机数 [5, 3,1,4,2,5,5]
那么咱们保留下[1,6,7],
3 个1 到 5 的随机数[2,4,1]
那么咱们保留下[6]
6 就是咱们此次生成的随机数。
产生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
判断一个天然数是不是某个数的平方。固然不能使用开方运算。
假设待判断的数字是 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中的乘法,因此速度会更快些。
给定一个未知长度的整数流,如何随机选取一个数?
方法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]为递增子序列,不然另外一部分是递增子序列。
而后判断要找的整数是否在递增子序列范围内。若是在,则使用普通的二分查找方法继续查找;若是不在,则重复上面的查找过程,直到找到或者失败为止。
谷歌面试题:给定两个已排序序列,找出共同的元素
不妨假设序列是从小到大排序的。定义两个指针分别指向序列的开始。
若是指向的两个元素相等,则找到一个相同的元素;若是不等,则将指向较小元素的指针向前移动。
重复执行上面的步骤,直到有一个指针指向序列尾端。若是两个数组大小差很少,用你的方法就好了,若是数组大小差得不少,就遍历小的,而后在大的里二分查找~
编程实现两个正整数的除法,固然不能用除法操做符。
// return x/y.
int div(const int x, const int 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;
}
微软面试题:计算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。
微软面试题:判断数组中是否包含重复数字
给定一个长度为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)。
微软面试题:删除链表中的重复项
一个没有排序的链表,好比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(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)
微软面试题:给出一种洗牌算法
给出洗牌的一个算法,并将洗好的牌存储在一个整形数组里。
假设数组Card[0 - 53]中的54个数对应54张牌,从第一张牌(i = 0)开始直到倒数第二张牌(i = 52),每次生成一个[ i, 53]之间的数r,将Card[i]和Card[r]中的数互换。
微软面试题:找到两个单向链表的第一个公共节点
若是两个单向链表有公共节点,则两个链表会构成Y型结构,最后一个节点相同。
咱们能够从头开始遍历两个链表,找到最后一个节点的指针,设为p_a,p_b。同时记录下两个链表的长度len_a,len_b(假设len_a >= len_b)。
若是p_a == p_b,则说明两个链表有公共节点,不然没有。
若是有公共节点,则第一个公共节点距起始节点的距离知足 len_a - start_a == len_b - start_b。
因此第一个可能的公共节点距起始节点的距离是 len_a - len_b, 0。咱们从这两个节点开始比较,直到找到第一个公共节点。
微软面试题:如何在链表里如何发现循环连接?
解答:
从链表的开始处,由两个指针A和B同时开始遍历链表。指针A每向前移动一步,指针B都向前移动两步。若是在移动了N步之后,指针A和B指向了同一个节点,则此链表中存在循环链表。
分析:
固然还能够在遍历的过程当中存储节点的地址,经过不断的比较地址来判断有没有循环链表。但这种算法会使用更多的内存。
若是考官比较变态,还能够直接考复制链表。若是复制前没有测试循环链表,那很差意思,只能扣分了
谷歌面试题:找到链表的倒数第m个节点
方法1:
首先遍历链表,统计链表的长度N。
而后再次遍历链表,找到第N-m个节点,即为倒数第m个节点。
方法2:
使用两个指针,并使它们指向的节点相距m-1个。
而后同时向前移动两个指针,当一个指针指最后一个节点时,第二个指针指向倒数第m个节点。
两个方法的复杂度都是O(n)。
可是当N较大而m较小时,方法2可能会更快一些。由于方法2能更好利用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)。
谷歌面试题:找到两个字符串的公共字符,并按照其中一个的排序
写一函数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;
` }
谷歌面试题:将无向无环连通图转换成深度最小的树
已知一个无向无环连通图T的全部顶点和边的信息,现须要将其转换为一棵树,要求树的深度最小,请设计一个算法找到全部知足要求的树的根结点,并分析时空复杂度。
最简单直接的方法就是把每一个节点都试一遍:
假设某个节点为根节点,计算树的深度。当遍历完全部节点后,也就找到了使树的深度最小的根节点。
但这个方法的复杂度很高。若是有n个节点,则时间复杂度为O(n^2)。
树的深度取决于根节点到最深叶节点的距离,因此咱们能够从叶节点入手。
叶节点会且只会和某一个节点连通(反之不成立,由于根节点也可能只和一个节点连通),因此咱们很容易找到全部可能的叶节点。
题目能够等价于找到了两个叶节点,使得两个叶节点之间的距离最远。根节点就是这两个叶节点路径的中间点(或者中间两个点的任意一个)。
咱们能够每次都将链接度为1的节点删掉,直到最后只剩下1个或2个节点,则这一个节点,或者两个节点中的任意一个,就是咱们要找的根节点。
谷歌面试题:将字符串中的小写字母排在大写字母的前面
有一个由大小写组成的字符串,如今须要对它进行修改,将其中的全部小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序)。
初始化两个int变量A和B,表明字符串中的两个位置。开始时A指向字符串的第一个字符,B指向字符串的最后一个字符。
逐渐增长A的值使其指向一个大写字母,逐渐减少B使其指向一个小写字母,交换A,B所指向的字符,而后继续增长A,减少B....。
当A>=B时,就完成了从新排序。
谷歌面试题:如何拷贝特殊链表
有一个特殊的链表,其中每一个节点不但有指向下一个节点的指针pNext,还有一个指向链表中任意节点的指针pRand,如何拷贝这个特殊链表?
拷贝pNext指针很是容易,因此题目的难点是如何拷贝pRand指针。
假设原来链表为A1 -> A2 ->... -> An,新拷贝链表是B1 -> B2 ->...-> Bn。
为了可以快速的找到pRand指向的节点,并把对应的关系拷贝到B中。咱们能够将两个链表合并成
A1 -> B1 -> A2 -> B2 -> ... -> An -> Bn。
从A1节点出发,很容易找到A1的pRand指向的节点Ax,而后也就找到了Bx,将B1的pRand指向Bx也就完成了B1节点pRand的拷贝。依次类推。
当全部节点的pRand都拷贝完成后,再将合并链表分红两个链表就能够了。
谷歌面试题:10分钟内看到一辆车的几率是多少?
若是在高速公路上30分钟内看到一辆车开过的概率是0.95,那么在10分钟内看到一辆车开过的概率是多少?(假设为常几率条件下)
假设10分钟内看到一辆车开过的几率是x,那么没有看到车开过的几率就是1-x,30分钟没有看到车开过的几率是(1-x)^3,也就是0.05。因此获得方程
(1-x)^3 = 0.05
解方程获得x大约是0.63。
百度面试题:从输入url到显示网页,后台发生了什么?
简单来讲有如下步骤:
1. 查找域名对应的IP地址。这一步会依次查找浏览器缓存,系统缓存,路由器缓存,ISP DNS缓存,根域名服务器。
2. 向IP对应的服务器发送请求。
3. 服务器响应请求,发回网页内容。
4. 浏览器解析网页内容。
固然,因为网页可能有重定向,或者嵌入了图片,AJAX,其它子网页等等,这4个步骤可能反复进行屡次才能将最终页面展现给用户。
百度面试题:设计DNS服务器中cache的数据结构
要求设计一个DNS的Cache结构,要求可以知足每秒5000以上的查询,知足IP数据的快速插入,查询的速度要快。(题目还给出了一系列的数据,好比:站点数总共为5000万,IP地址有1000万,等等)
DNS服务器实现域名到IP地址的转换。
每一个域名的平均长度为25个字节(估计值),每一个IP为4个字节,因此Cache的每一个条目须要大概30个字节。
总共50M个条目,因此须要1.5G个字节的空间。能够放置在内存中。(考虑到每秒5000次操做的限制,也只能放在内存中。)
能够考虑的数据结构包括hash_map,字典树,红黑树等等。
百度面试题:将多个集合合并成没有交集的集合
给定一个字符串的集合,格式如:{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个集合没有进行合并。
百度面试题:用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;
` }
` }
百度面试题:找出给定字符串对应的序号
序列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
百度面试题:找出第k大的数字所在的位置
写一段程序,找出数组中第k大小的数,输出数所在的位置。例如{2,4,3,4,7}中,第一大的数是7,位置在4。第二大、第三大的数都是4,位置在一、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的位置对应的整数就是不能由其它数生成的。
百度面试题:对正整数,算获得1须要操做的次数
实现一个函数,对一个正整数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)。
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!后面的0的个数
容易理解,题目等价于求因子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)。
百度面试题:找出被修改过的数字
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。
百度面试题:在100w个数中找最大的前100个数
应该使用某种数据结构保存迄今最大的100个数。每读到一个新数时,将新数和保存的100个数中的最小一个相比较,若是新数更大些,则替换。这样扫描一遍100w个数也就得到了最大的100个数。
对于保存的100个数的数据结构,应该在最小复杂度的条件下知足
1)能够得到最小的数;
2)将最小数替换为另外一个数后能够从新调整,使其能够知足条件1。
可见小根堆能够知足这些条件。
因此应该采用小根堆+扫描的方法。
方法1:相似《算法导论》中用二分法求第K大数,理想TC是O(n)。
百度面试题:正向最大匹配分词,怎么作最快?
用全部词生成一个字典树,匹配的过程就是查字典的过程。
假设咱们有两个词”百度“,”百家姓“,那么生成的字典树就是:
百---度*
|
|-----家----姓*
其中“度”和“姓”旁边的星号表示这是一个有效词。
对于句子“百度面试题“,首先在字典中找”百“,找到了;继续向下查找”度“,又找到了;继续向下查找”面“,没有找到。那么”百度“就是咱们分出来的第一个词。
还能够用hash_map来作。
首先用全部的词生成一个hash_map,假设咱们有两个词“百度,“百家姓”,那么生成hash_map以下:
{
百:0
百度:1
百家:0
百家姓:1
}
其中值为0表示对应的key不是一个词,但有更长的词包括这个key;值为1表示这是一个词。
对于句子“百度面试题”,首先在hash_map中查找“百”,找到对应值为0,继续;查找“百度”,找到对应值为1,说明这是一个词,记下来并继续;查找“百度面”,没有找到,说明没有更长的词包含“百度面”。因此“百度”就是咱们分出来的第一个词。
和字典法相比,hash_map法可能会用到更多的存储空间(由于有些字,好比“百”字,都存储了屡次。但这还取决于字典树的具体实现),但程序设计会更加简单,不容易出错。
session和cache的区别是什么?
session是针对单个链接(会话)来使用的,主要存储和链接相关的上下文信息,好比登陆信息等等。
cache是应用程序级的,主要用来缓存计算结果,减轻服务器负担,并加快响应速度。
百度面试题:找出数组中出现次数超过一半的数
答案:
建立一个hash_map,key为数组中的数,value为此数出现的次数。遍历一遍数组,用hash_map统计每一个数出现的次数,并用两个值存储目前出现次数最多的数和对应出现的次数。
这样能够作到O(n)的时间复杂度和O(n)的空间复杂度,知足题目的要求。
可是没有利用“一个数出现的次数超过了一半”这个特色。也许算法还有提升的空间。
答案2:
使用两个变量A和B,其中A存储某个数组中的数,B用来计数。开始时将B初始化为0。
遍历数组,若是B=0,则令A等于当前数,令B等于1;若是当前数与A相同,则B=B+1;若是当前数与A不一样,则令B=B-1。遍历结束时,A中的数就是要找的数。
这个算法的时间复杂度是O(n),空间复杂度为O(1)。
百度面试题:如何找出字典中的兄弟单词
给定一个单词a,若是经过交换单词中字母的顺序能够获得另外的单词b,那么定义b是a的兄弟单词。如今给定一个字典,用户输入一个单词,如何根据字典找出这个单词有多少个兄弟单词?
答案:
使用hash_map和链表。
首先定义一个key,使得兄弟单词有相同的key,不是兄弟的单词有不一样的key。例如,将单词按字母从小到大从新排序后做为其key,好比bad的key为abd,good的key为dgoo。
使用链表将全部兄弟单词串在一块儿,hash_map的key为单词的key,value为链表的起始地址。
开始时,先遍历字典,将每一个单词都按照key加入到对应的链表当中。当须要找兄弟单词时,只需求取这个单词的key,而后到hash_map中找到对应的链表便可。
这样建立hash_map时时间复杂度为O(n),查找兄弟单词时时间复杂度是O(1)。
网易面试题:new/delete和malloc/free的区别
new/delete:给定数据类型,new/delete会自动计算内存大小,并进行分配或释放。若是是对类进行操做,new/delete还会自动调用相应的构造函数和析构函数。
malloc/free:没有进行任何数据类型检查,只负责分配和释放给定大小的内存空间。
有些状况下,new/delete和malloc/free都不能知足性能的要求,咱们须要自建内存分配来提升效率。好比,若是程序须要动态分配大量很小的对象,咱们能够一次分配能够容纳不少小对象的内存,将这些小对象维护在链表中,当程序须要时直接从链表中返回一个。还有一点,new返回指定类型的指针;而malloc返回void*,必须强制类型转化。
有个比较有意思的地方是:int *p=(void*)malloc(1);能够编译并运行。
网易面试题:没有拷贝构造函数和重载=运算符的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参数:尽可能使用指针。
网易面试题:写一段程序,实现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;
` }
` }
网易面试题:给出若干个单词,组成字典,要求查找速度最快。
为使查找速度最快,能够要使用hash_map。
若是每一个单词还有对应的解释和例句,能够将解释和例句对应的指针存放在hash_map的值中。或许能够尝试使用 TRIE 结构。
迅雷面试题:门面模式的解释、适用场合?
门面模式又被称为外观模式,为子系统中的一组接口提供一个一致的界面,该模式定义了一个高层接口,使得这个子系统更加容易使用。
举个例子:在作项目或产品的过程当中进行跨部门合做的时候,每一个部门都有个相应的接口人,那么咱们只需和对应部门的接口人交互便可。
适用场合:
为一个复杂子系统提供一个简单接口:子系统每每由于不断演化而变得愈来愈复杂,使用门面模式可使得子系统更具备可复用性。
子系统的独立性:引入门面模式将一个子系统与它的客户端以及其余子系统分离,能够提升子系统的独立性和可移植性。
层次化结构:在构建一个层次化的系统时,可使用 门面模式定义系统中每一层的入口。若是层与层之间是相互依赖的,则能够限定它们仅经过门面进行通讯,简化层与层之间的依赖关系。
迅雷面试题:AJAX的原理、如何实现刷新及其优势
AJAX即“Asynchronous JavaScript and XML”(异步JavaScript和XML),是指一种建立交互式网页应用的网页开发技术。
使用了AJAX技术的网页,利用Javascript和服务器通讯,获取数据,而后再经过修改网页的DOM中的某些元素来实现刷新网页的特定部分。
使用了AJAX技术后,因为只须要更新网页的一部分,而不是所有,因此和服务器交互的数据比较少。这就下降了服务器的负载,并提升了用户端的响应速度。另外,AJAX并不须要在浏览器中安装插件。
迅雷面试题:数组与链表的区别?
在数组中,元素在内存中连续存放。对于访问操做,因为元素类型相同,占用内存相同,因此能够经过数组的下标计算出元素所在的内存地址,便于快速访问。但对于插入或删除操做,须要移动大量元素,因此速度比较慢。
在链表中,元素在内存中没有连续存放,而是经过元素中的指针将各个元素链接在一块儿。对于访问操做,须要从链表头部开始顺序遍历链表,直到找到须要的元素,因此速度比较慢。对于插入或删除操做,只需修改元素中的指针便可完成,速度比较快。
因此,若是须要频繁访问数据,不多插入删除操做,则使用数组;反之,若是频繁插入删除,则应使用链表。
迅雷面试题:最快的排序法的性能,并列举至少三个
最快的排序算法是O(N*lgN)。,快排序,堆排序,归并排序
迅雷面试题:合并用户基本信息和看电影的记录
如何有效合并两个文件:一个是1亿条的用户基本信息,另外一个是用户天天看电影连续剧等的记录,5000万条。其中内存只有1G。
显然内存不能同时存下全部的数据,因此考虑分而治之的思想。
假设1K Byte能够保存一个用户的基本信息和看电影记录。咱们能够将基本信息和看电影记录都按照hash(user_name)%100的余数各分红100个小文件。利用1G内存,咱们能够每次只处理一对小文件,而后将结果输出到一个文件中便可。
在处理一对小文件时,能够利用key为用户名的hash_map将基本信息和看电影记录合并在一块儿。
迅雷面试题:c语言中不一样include方法的差异
#include "filename.h" 首先在程序原文件所在目录下查找,若是找不到,再到系统目录中查找。
#include <filename.h> 直接去系统目录中查找。
在1亿条用户记录里,如何快速查询统计出看了5个电影以上的用户?
构建一个hash map,key为用户名,value为已经看过的电影数量。
遍历全部用户记录,而后根据用户名和已经看过电影数量的状况进行处理:
- 若是用户名不在hash map中,则添加对应用户名,并将值设为1。
- 若是用户名对应的值小于5,则将值加1。若是加1后值为5,则输出此用户名。
- 若是用户名对应的值等于5,则不进行任何操做。
oracle面试题:数据库冷备份和热备份的不一样点以及各自的优势
热备份针对归档模式的数据库,在数据库仍旧处于工做状态时进行备份。而冷备份指在数据库关闭后,进行备份,适用于全部模式的数据库。热备份的优势在于当备 份时,数据库仍旧能够被使用而且能够将数据库恢复到任意一个时间点。冷备份的优势在于它的备份和恢复操做至关简单,而且因为冷备份的数据库能够工做在非归 档模式下,数据库性能会比归档模式稍好。(由于没必要将archive log写入硬盘)
华为面试题:IP,TCP和UDP协议的定义和主要做用
IP协议是网络层的协议。IP协议规定每一个互联网网上的电脑都有一个惟一的IP地址,这样数据包就能够经过路由器的转发到达指定的电脑。但IP协议并不保证数据传输的可靠性。
TCP协议是传输层的协议。它向下屏蔽了IP协议不能可靠传输的缺点,向上提供面向链接的可靠的数据传输。
UDP协议也是传输层的协议。它提供无链接的不可靠传输。
华为面试题:全局变量和局部变量有什么区别
全局变量是整个程序均可访问的变量,生存期从程序开始到程序结束;局部变量存在于模块中(好比某个函数),只有在模块中才能够访问,生存期从模块开始到模块结束。
全局变量分配在全局数据段,在程序开始运行的时候被加载。局部变量则分配在程序的堆栈中。所以,操做系统和编译器能够经过内存分配的位置来知道来区分全局变量和局部变量。全局变量和局部变量的区别是在存储器中位置不一样,具体说,全局变量存储在数据段中,局部变量通常来讲在堆栈段
华为面试题:析构函数和虚函数的用法和做用?
析构函数是在类对象消亡时由系统自动调用。主要用来作对象的清理工做,好比来释放对象申请的动态空间。
基类中用virtual修饰的函数称为虚函数。在派生类中能够对虚函数进行从新定义,这样一样的函数接口能够在不一样的派生类中对应不一样的实现。当经过基类的指针来调用虚函数时,程序会根据指针实际指向的对象来决定调用哪一个实现。
华为面试题:如何引用一个已经定义过的全局变量?
能够用引用头文件的方式,也可使用extern关键字。
用引用头文件方式,若是将那个变量写错了,那么在编译期间会报错。
用extern方式,若是将变量名写错了,那么在编译期间不会报错,而在链接期间报错。
华为面试题:c语言中局部变量可否和全局变量重名?
局部变量能够与全局变量同名。在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。要用全局变量,须要使用"::"。
对于有些编译器而言,在同一个函数内能够定义多个同名的局部变量,好比在两个循环体内都定义一个同名的局部变量,而那个局部变量的做用域就在那个循环体内。
完美时空面试题:memcpy 和 memmove 有什么区别?
memcpy和memmove都是将源地址的若干个字符拷贝到目标地址。
若是源地址和目标地址有重叠,则memcpy不能保证拷贝正确,但memmove能够保证拷贝正确。
例如:
char src[20];
// set src
char* dst = src + 5;
此时若是要从src拷贝10个字符到dst,则么memcpy不能保证拷贝正确,可是memmove能够保证。
雅虎面试题: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方法更加安全。