问题一:现有海量日志数据,要提取出某日访问百度次数最多的那个IP(可以将题干简化,假设日志中仅包含IP数据,也就是说待处理的文件中包含且仅包含全部的访问IP,但内存空间有限,不能全部加载,假设只有512MB)
解决方案:
这是一道典型的分治思想的题目,这种问题处理起来套路比较固定,对于大部分的数据量比较大的前提的问题而言,分治都是一个可选的解决方案,但不一定是最优的,解决方法基本划分为三步走:
第一:如何有效的划分数据
第二:如何在子集上解决问题
第三:如何合并结
那么对于本问题就显得比较明显了:
首先解决如何划分,由于IP地地址范围从000.000.000.000~255.255.255.255共有2^32个大约4GB,那么我们可以通过取模的方式进行划分,直接将四段数据相连就得到一个IP对应的数字A,再对A模1024(模数取多少可以自己指定,保证每个小文件能被放到内存就好),这样大文件被划分成小文件了,并且相同的IP一定被划分到相同的文件中。
其次解决每个小文件中TOP1的问题:
这里可以用很多方式进行处理,比如你可以构造自己的HashMap,key为IP,value为当前出现次数,最后找到value最大的Key即为当前文件中出现次数最多的IP。
最后要解决结果合并问题:
这里直接将1024个子文件的统计结果进行比较就好,不用排序,直接选择最大的一个就好。
注意:这里第二步是影响效率的地方,读者可以想想如何优化这里的Wordcount,可以用trie树等
问题二:有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
解决方案:细心的读者可以发现这个和第一个问题应该没有太大别,区别就在于第一个问题是Top1,这个问题是TOP100。
那么对于这个问题,主要考虑的是如何划分(每个文件要小于1M),这里可以考虑划分为2028份。
对于第二步可以考虑用堆排序
即常用的TopN手段。
第三部就是更为常见的归并排序
问题三:给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?
解决方案:这种题目,我首先想到的就是布隆过滤器(BloomFilter)
当然使用布隆过滤器存在一定的错误率,所以面试者可以和面试官进行进一步的沟通,看是否允许有一定的错误率。
如果使用布隆过滤器,那么问题就很好办了,4G的内存足以容纳300多亿的bit,所以足够处理了,先将a文件中的url都放入布隆过滤器,之后遍历b文件,对每个url都询问布隆过滤器看其是否已经存在,如果存在,则此条URL输入结果文件。
关于布隆过滤器使用空间和错误率的关系可以看这篇博文
第二种方案就是采用更为通用的但对于这个问题效率较低的分治思想,对a,b进行分片,a,b文件大小都大约320G,那么每个文件都切分成1G的320个小文件。
lista->{a1,a2,a3...a320}
listb->{b1,b2,b3...b320}
伪代码如下:
resultlist = []
for tmpfile in lista:
for infile in listb:
for url in tmpfile:
if url in infile:
resultlist.add(url)
当然这个在实现的时候可以进一步优化,首先可以并行处理(这里只用了2G内存,所以至少可以2个线程并行)
其次,可以对a中URL进行小部分判重处理,如果之前已经有了,
就不必处理。
问题四:有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。
解决方案:这种题又是典型的wordcount问题,首先这种题不用说都是内存有限制(最好问问面试官,内存限制是多少)。如果没内存限制直接一个trie树就搞定了(因为一般来说query的重复都比较多)。
如果内存限制比较大,那么可以尝试屡试不爽的分治方法。但在使用之前需要先对原来的文件进行一下处理通过hash(url)%A,这里A只分成多少分,这里可以也取10,就是将原来的10个文件转为新的10个文件,如果内存限制比较大,那么这个A可以根据内存大小调整,要让1G/A <= 内存大小。
接下来就是要对每个小文件进行统计,可以用hashmap(url,count),也可以用trie树。
之后采用归并排序的方法得到最终结果。
问题五:怎么在海量数据中找出重复次数最多的一个?
解决方案:如果看了前面四个题目,这里相信读者应该已经会自己解决了。
使用屡试不爽的分治思路,先划分成小文件,之后分别统计,之后再选取max,读者可以思考如何优化小文件内的处理方法。
问题六:在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。
解决方案:首先对于这种问题基本上都可以采用分治的思想,先进行划分后进行单独处理,最后进行结果合并。但对于这道题目显然分治思想比较牵强,效率不高。
那么再审视一下这个题目,其只需要找到不重复的整数,无需什么排序,而且又限定在整数,那么显然可以考虑位集的思想。
2.5亿个整数,显然可以用一个250M的位集表示。
但简单的位集只能表示存在与否,因为每个bit表示一个整数,1个bit只能表示两种情况,但在本题目中不但需要表示存在性,还需要表示是否多次出现,即需要寻找出现次数>=2,那么至少需要两个bit才能表示这种情况,00->不出现,01->出现一次,10->出现至少2次,这样就能统计出那些数据出现重复。
总结一下就是本题目可以用位集的变形,每个数用2个bit表示,那么总共需要250M*2b = 500Mb的内存,完全满足题目,而且效率还是o(n)。
问题七:一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
解决方案:分治思想这里就简要阐述一下,可以利用trie树进行词频统计,之后采用TopN的解决方法,关于排序的问题可以看这篇博文
堆排序的时间复杂度是n(longk),n是数据集合大小,k是topK中的k,那么复杂度是max(n*len(e),nlogk),其中len(e)表示字符串平均大小。
这里想讲一下另一种稍显技巧的方法,即用变形的位集表示,由于题目中只有1w的数据,那么一个词最多出现1w词,那么用18个bit就可以表示最多出现的次数,说道这里读者可以回顾一下第六个题目了,其实和题目六很相似的,这里每个数字用18个bit表示,那么剩下的问题就是解决如何将每个词转化为1~10000的数字,这里读者可以自行YY或者自行搜索,方法有很多。关键的问题是要想到可以用这种方式来解决,那么复杂度显然就是o(n)
问题八:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?
(PS:腾讯面试题)
解决方案:这个题目限定的比较明确了,整数、不重复,那么理所当然可以用bitMap的思想去解决了。
申请一个40亿的bit数组,初始化为0,遍历数据集合,有的占位1,给定待验证的数据,直接看相应位置的是1还是0即可。
当然有人可能觉得40亿的bit内存装不下啊,那怎么办,当然可以分治了,写到N个文件中总可以了吧,根据待验证的数,直接查找相应的文件,在相应的文件中查看是1还是0。
比如分成1W份,被分割的文件按下标存储:
file1,file2,file3 ... file10000
那么相当于每个文件存储4Mb的位,待验证的数据为a,那么目标文件的index为
index*400000<=a<=(index+1)*400000
求出index,再在file(index)中查找。
问题九:上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。
解决方案:典型的TopN问题,请参考前面的问答题。
问题十: 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请统计出最热门的10个查询串,要求使用的内存不能超过1G。
解决方案:典型的TopN问题,请参考前面的问题的答案,可以考虑使用分治的思想。
基本上处理海量数据的问题,分治思想都是能够解决的,只不过一般情况下不会是最优方案,但可以作为一个baseline,可以逐渐优化子问题来达到一个较优解。传统的归并排序就是分治思想,涉及到大量无法加载到内存的文件、排序等问题都可以用这个方法解决。
适用场景:数据量大无法加载到内存
技能链接:归并排序
个人感觉Hash是最为粗暴的一种方式,但粗暴却高效,唯一的缺点是耗内存,需要将数据全部载入内存。
适用场景:快速查找,需要总数据量可以放入内存
位集这种思想其实简约而不简单,有很多扩展和技巧。比如多位表示一个数据(能够表示存在和数量问题),BloomFilter(布隆过滤器就是一个典型的扩展),在实际工作中应用场景很多,比如消息过滤等,读者需要掌握,但对于布隆过滤器使用有一些误区和不清楚的地方,读者可以看下面这篇博客避免这些性能上的误区。
适用场景:可进行数据的快速查找,判重
技能链接:布隆过滤器使用的性能误区
堆排序是一种比较通用的TopN问题解决方案,能够满足绝大部分的求最值的问题,读者需要掌握堆的基本操作和思想。
适用场景:处理海量数据中TopN的问题(最大或最小),要求N不大,使得堆可以放入内存
技能链接:排序算法-Heap排序