大数据和空间限制问题

大数据和空间限制问题

1. 只用2GB内存在20亿个整数中找到出现次数最多的数

2. 40亿个非负整数中找到没出现的数

3. 找到100亿个URL中重复的URL以及搜索词汇的top K问题

4. 40亿个非负整数中找到出现两次的数和所有数的中位数

5. 一致性哈希算法的基本原理

6. 统计论坛在线人数分布

7. 找到1到10w中没有出现的两个数字

8. 判断数字是否出现在40亿个数中?

9. 从10G个数中找到中数


1.题目:有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数。
              内存限制为2GB。
解答:
       想要在很多整数中找到出现次数最多的数,通常的做法是使用哈希表对出现的每一个数做词频统计,哈希表的key是某一个整数,value是这个数出现的次数。就本题来说,一共有20亿个数,哪怕只是一个数出现了20亿次,用32位的整数也可以表示其出现的次数而不会产生溢出,所以哈希表的key需要占用4B,value也是4B。那么哈希表的一条记录占用8B,当哈希表记录数为2亿个时,需要至少1.6GB的内存。
       但如果20亿个数中不同的数超过2亿种,最极端的情况是20亿个数都不同,那么哈希表中可能需要产生20亿条记录,这样内存会不够用,所以一次性用哈希表统计20亿个数的办法是有很大风险的。
       解决办法是把包含20亿个数的大文件用哈希函数分成16个小文件,根据哈希函数的性质,同一种数不可能被哈希到不同的小文件中,同时每个小文件不同的数一定不会大于2亿种,假设哈希函数足够好。然后对每一个小文件用哈希表统计其中每种数出现的次数,这样我们就得到了16个小文件中各自出现次数最多的数,还有各自的次数统计。接下来只要选出16个小文件各自的第一名中谁出现的次数最多即可。
       把一个大的集合通过哈希函数分配到多态机器中,或者分配到多个文件中,这种技巧是处理大数据面试题时最常用的技巧之一。但是到底分配多少台机器、分配到多少文件,在解题时一定要确定下来。可能是在与面试官沟通的过程中由面试官指定,也可能是根据具体的限制来确定,比如本题确定分成16个文件,就是根据内存限制2GB的条件来确定的。

2.题目:32位无符号整数的范围是0-4294967295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然有没出现过的数。可以使用最多1GB的内存,怎么找到所有没出现过的数?
进阶:内存限制为10MB,但是只用找到一个没出现过的数即可。
解答:
       原问题。如果用哈希表来保存出现过的数,那么如果40亿个数都不同,则哈希表的记录为40亿条,存一个32位整数需要4B,所以最差情况下需要40亿*4B=160亿字节,大约需要16GB的空间,这是不符合要求的。
       哈希表需要占用很多空间,我们可以使用bit map的方式来表示数出现的情况。具体地说,是申请一个长度为4294967295的bit类型的数组bitArr,bitArr上的每个位置只可以表示0或1状态。8个bit位1B,所以长度为4294967295的bit类型的数组占用500MB的空间。
       怎么使用这个bitArr数组呢?就是遍历这40亿个无符号数,例如,遇到7000,就把bitArr[7000]设置为1,遇到所有的数时,就把bitArr相应位置的值设置为1。
       遍历完成后,再依次遍历bitArr,哪个位置上的值没有被设置为1,哪个数就不在40亿个数中。例如,发现bitArr[8001]==0,那么8001就是没出现过的数,遍历完bitArr之后,所有没出现的数就都找出来了。
       进阶问题。现在只有10MB的内存,但也只要求找到其中一个没出现过的数即可。首先,0-4294967295这个范围是可以平分成64个区间的,每个区间是67108864个数,例如:第0区间(0-67108863)、第1区间(67108864-134217728)、第i区间(67108864*i-67108864*(i+1)-1),...。因为一共只有40亿个数,所以,如果统计落在每一个区间上的数有多少,肯定有至少一个区间上的计数少于6710884.利用这一点可以找出其中一个没出现过的数。具体过程为:
       第一次遍历时,先申请长度为64的整型数组countArr[0...63],countArr[i]用来统计区间i上的数有多少。遍历40亿个数,根据当前数是多少来决定哪一个区间上的计数增加。例如,如果当前数是3422552090,3422552090/67108864=51,所以第51区间上的计数增加countArr[51]++。遍历完40亿个数之后,遍历countArr,必然会有某一个位置上的值(countArr[i])小于67108864,表示第i区间上至少有一个数每出现过。我们肯定会至少找到一个这样的区间。此时使用的内存就是countArr的大小(64*4B),是非常小的。
       假设我们找到第37区间上的计数小于67108864,以下为第二次遍历的过程:
       1.申请长度为67108864的bit map,这占用大约8MB的空间,记为bitArr[0..67108863];
       2.再遍历一次40亿个数,此时的遍历只关注落在第37区间上的数,记为num(num/67108864==37),其他区间的数全部忽略。
       3.如果步骤2的num在第37区间上,将bitArr[num-67108864*37]的值设置为1,也就是只做第37区间上的数的bitArr映射。
       4.遍历完40亿个数之后,在bitArr上必然存在没被设置为1的位置,假设第i个位置上的值没被设置为1,那么67108864*37+i这个数就是一个没出现过的数。
总结一下进阶的解法:
       1.根据10MB的内存限制,确定统计区间的大小,就是第二次遍历时bitArr大小。
       2.利用区间计数的方式,找到那个计数不足的空间,这个区间上肯定有没出现的数。
       3.对这个区间上的数做bit map映射,再遍历bit map,找到一个没出现的数即可。

3. 题目:有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL。
    补充题目:某搜索公司一天的用户搜索词汇是海量(百亿数据量),请设计一种求出每天最热top100词汇的可行办法。

解答:原问题的解法使用解决大数据问题的一种常规方法:把大文件通过哈希函数分配到机器,或者通过哈希函数把大文件拆成小文件。一直进行这种划分,直到划分的结果满足资源限制的要求。首先,你要向面试官询问在资源上的限制有哪些,包括内存、计算时间等要求。在明确了限制要求之后,可以将每条URL通过哈希函数分配到若干机器或者拆分成若干小文件,这里的“若干”由具体资源限制计算出精确的数量。
       例如将100亿字节的大文件通过哈希函数分配到100台机器上,然后每一台机器分别统计给自己的URL中是否有重复的URL,同时哈希函数的性质决定了同一条URL不可能分给不同的机器;或者在单机上将大文件通过哈希函数拆成1000个小文件,对每一个小文件再利用哈希表遍历,找出重复的URL;或者在分给机器或拆完文件之后,进行排序,排序过后在看是否有重复的URL出现。总之,牢记一点,很多大数据问题都离不开分流,要么是哈希函数把大文件的内容分配给不同的机器,要么是哈希函数把大文件拆成小文件,然后处理每一个小数量的集合。
       补充问题最开始还是用哈希分流的思路来处理,把包含百亿数量的词汇文件分流到不同的机器上,具体多少台机器由面试官规定或者由更多的限制来决定。对每一台机器来说,如果分到的数据量依然很大,比如,内存不够或其他问题,可以再用哈希函数把每台机器的分流文件拆成更小的文件处理。处理每一个小文件的时候,哈希表统计每种词及其词频,哈希表记录建立完成后,再遍历哈希表,遍历哈希表的过程中使用大小为100的小根堆来选出每一个小文件的top100(整体未排序的top)。每一个小文件都有自己词频的小根堆(整体未排序的top100),将小根堆里的词按照词频排序,就得到了每个小文件的排序后top100.然后把各个小文件排序后的top100进行外排序或者继续利用小根堆,就可以选出每台机器上的top100.不同机器之间的top100再进行外排序或者继续利用小根堆,最终求出整个百亿数据量中的top100。
       对于top K的问题,除哈希函数分流和用哈希表做词频统计外,还经常用堆结构和外排序的手段进行处理。

4. 题目:32位无符号数的范围是0-4294967295,现有40亿个无符号整数,可以使用最多1GB内存,找出所有出现了两次的数。
    补充题目:可以使用最多10MB的内存,怎么找到这40亿个整数的中位数?

解答:
       对于原问题,可以用bit map的方式表示出现的情况。具体地说,是申请一个长度为4294967295*2的bit类型的数组bitArr,用2个位置表示一个数出现的词频,1B占用8个bit,所以长度为4294967295*2的bit类型的数组占用1GB空间。怎么使用这个bitArr数组呢?遍历这40亿个无符号数,如果初次遇到Num,就把bitArr[num*2+1]和bitArr[num*2]设置为01,如果第二次遇到Num,就把bitArr[num*2+1]和bitArr[num*2]设置为10,如果第三次遇到num,就把bitArr[num*2+1]和bitArr[num*2]设置为11.以后再遇到num,发现此时bitArr[num*2+1]和bitArr[num*2]已经被设置为11,就不再做任何设置。遍历完成后,再依次遍历bitArr,如果发现bitArr[i*2+1]和bitArr[i*2]设置为10,那么i就是出现了两次的数。
     对于补充问题,用分区间的方式处理,长度为2MB的无符号整型数组占用的空间为8MB,所以讲区间的数量定为4294967295/2M,向上取整为2148个区间,第0区间为0-2M-1,第1区间为2M-4M-1,第i区间为2M*i-2M*(i+1)-1......
申请一个长度为2148的无符号整型数组arr[0..2147],arr[i]表示第i区间有多少个数。arr必然小于10MB。然后遍历40亿个数,如果遍历到当前数为num,先看Num落在哪个区间上(num/2M),然后对应的进行arr[num/2M]++操作。这样遍历下来,就得到了每一个区间的数的出现状况,通过累加每个区间的出现次数,就可以找到40亿个数的中位数(也就是第20亿个数)到底落在哪个区间上。比如,0-K-1区间上的个数为19.998亿,但是发现当加上第K个区间上的个数之后就超过了20亿,那么可以知道第20亿个数是第K区间上的数,并且可以知道第20亿个数是第K区间上的第0.002亿个数。
       接下来申请一个长度为2MB的无符号整型数组countArr[0...2M-1],占用空间8MB。然后遍历40亿个数,此时只关心处在第K区间的数记为numi,其他的数省略,然后将countArr[numi-K*2M]++,也就是只对第K区间的数做频率统计。这次遍历完40亿个数之后,就得到了第K区间的词频统计结果countArr,最后只在第K区间上找到第0.002亿个数即可。

5. 题目:工程师常使用服务器集群来设计和实现数据缓存,以下是常见策略:
        1.无论是添加、查询还是删除数据,都先将数据的id通过哈希函数转换成一个哈希值,记为key。
        2.如果目前机器有N台,则计算key%N的值,这个值就是该数据所属的机器编号,无论是添加、删除还是查询操作,都只在这台机器上进行。

请分析这种缓存策略可能带来的问题,并提出改进的方案。
解答
       题目中描述的缓存策略的潜在问题是如果增加或删除机器时(N变化时)代价会很高,所有的数据都不得不根据id重新计算一遍哈希值,并将哈希值对新的机器数进行取模操作,然后进行大规模的数据迁移。
       为了解决这些问题,下面介绍一下一致性哈希算法,这是一种很好地数据缓存设计方案。我们假设数据的id通过哈希函数转换成的哈希值范围是2^32,也就是0~2^32-1的数字空间中。现在我们可以将这些数字头尾相连,想象成一个闭合的环形,那么一个数据id在计算出哈希值之后认为对应到环中的一个位置上。
       接下来想象有三台机器也处在这样一个环中,这三台机器在环中的位置根据机器id计算出的哈希值来决定。那么一条数据如何确定归属哪台机器呢?首先把该数据的id用哈希函数算出哈希值,并映射到环中的相应位置,然后顺时针寻找离这个位置最近的机器,那台机器就是该数据的归属。
       增加机器时的处理。假设有两台机器(m1,m2)和三个数据(data1、data2、data3),如果此时想加入新的机器m3,同时算出机器m3的id在m1与m2右半侧的环中,那么在没有添加m3之前,从m1到现在m3位置上的这一段是m2的掌管范围的一部分;添加m3之后则统一归属m3,同时要把这一段旧数据从m2迁移到m3上。由此可见,添加机器时的调整代价是比较小的。在删除机器时也一样,只要把删除机器的数据全部复制到顺时针找到的下一台机器上即可。
       机器负载不均时的处理。如果机器较少,很有可能造成机器在整个环上的分布不均匀,从而导致机器之间的负载不均衡。
      为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一台机器通过不同的哈希函数计算出多个哈希值,对多个位置都放置一个服务节点,称为虚拟节点。具体做法是可以在机器ip或主机名的后面增加编号或端口号来实现。比如对于有m1和m2两个节点来说,可以为每台机器计算两个虚拟节点,分别计算m1-1、m1-2、m2-1和m2-2的哈希值,于是形成四个虚拟节点,节点数变多了,根据哈希函数的性质,平衡性自然会变好。此时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射。
       当某一条数据计算出归属于m1-2时,再根据上表的跳转,数据将最终属于实际的m1节点。基于一致性哈希的原理有多种具体实现,包括Chord算法、KAD算法。

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

7. 找出1-10w中没有出现的两个数字
(1)位图,申请10w个bit的空间,每个bit代表一个数字是否出现过。开始时将这10w个bit都初始化为0,表示所有数字都没有出现过。然后依次读入已经打乱循序的数字,并将对应的bit设为1。当处理完所有数字后,根据为0的bit得出没有出现的数字。
(2)首先计算1到10w的和,平方和。然后计算给定数字的和,平方和。两次的到的数字相减,可以得到这两个数字的和,平方和。所以我们有x + y = n  x^2 + y^2 = m  解方程可以得到x和y的值。

8. 给40亿个不重复的unsignedint的整数,没排过序的,然后再给几个数,如何快速判断这几个数是否在那40亿个数当中?
       unsignedint 的取值范围是0到2^32-1。我们可以申请连续的2^32/8=512M的内存,用每一个bit对应一个unsigned int数字。首先将512M内存都初始化为0,然后每处理一个数字就将其对应的bit设置为1。当需要查询时,直接找到对应bit,看其值是0还是1即可。

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

你可能感兴趣的:(面试,常用算法)